public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 1/9] Pass all scan keys to BRIN consistent function at once
73+ messages / 10 participants
[nested] [flat]

* [PATCH 1/9] Pass all scan keys to BRIN consistent function at once
@ 2020-09-12 13:07  Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Tomas Vondra @ 2020-09-12 13:07 UTC (permalink / raw)

Passing all scan keys to the BRIN consistent function at once may allow
elimination of additional ranges, which would be impossible when only
passing individual scan keys.

The code continues to support both the original (one scan key at a time)
and new (all scan keys at once) approaches, depending on whether the
consistent function accepts three or four arguments.

This modifies the existing BRIN opclasses (minmax, inclusion) although
those don't really benefit from this change. The primary purpose of this
is to allow more advanced opclases in the future.

Author: Tomas Vondra <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 src/backend/access/brin/brin.c           | 150 +++++++++++++++-----
 src/backend/access/brin/brin_inclusion.c | 170 +++++++++++++++--------
 src/backend/access/brin/brin_minmax.c    | 121 +++++++++++-----
 src/backend/access/brin/brin_validate.c  |   4 +-
 src/include/catalog/pg_proc.dat          |   4 +-
 5 files changed, 324 insertions(+), 125 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 27ba596c6e..dc187153aa 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -390,6 +390,9 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	BrinMemTuple *dtup;
 	BrinTuple  *btup = NULL;
 	Size		btupsz = 0;
+	ScanKey	  **keys;
+	int		   *nkeys;
+	int			keyno;
 
 	opaque = (BrinOpaque *) scan->opaque;
 	bdesc = opaque->bo_bdesc;
@@ -411,6 +414,61 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	 */
 	consistentFn = palloc0(sizeof(FmgrInfo) * bdesc->bd_tupdesc->natts);
 
+	/*
+	 * Make room for per-attribute lists of scan keys that we'll pass to the
+	 * consistent support procedure.
+	 */
+	keys = palloc0(sizeof(ScanKey *) * bdesc->bd_tupdesc->natts);
+	nkeys = palloc0(sizeof(int) * bdesc->bd_tupdesc->natts);
+
+	/*
+	 * Preprocess the scan keys - split them into per-attribute arrays.
+	 */
+	for (keyno = 0; keyno < scan->numberOfKeys; keyno++)
+	{
+		ScanKey		key = &scan->keyData[keyno];
+		AttrNumber	keyattno = key->sk_attno;
+
+		/*
+		 * The collation of the scan key must match the collation
+		 * used in the index column (but only if the search is not
+		 * IS NULL/ IS NOT NULL).  Otherwise we shouldn't be using
+		 * this index ...
+		 */
+		Assert((key->sk_flags & SK_ISNULL) ||
+			   (key->sk_collation ==
+				TupleDescAttr(bdesc->bd_tupdesc,
+							  keyattno - 1)->attcollation));
+
+		/* First time we see this index attribute, so init as needed. */
+		if (!keys[keyattno-1])
+		{
+			FmgrInfo   *tmp;
+
+			/*
+			 * This is a bit of an overkill - we don't know how many
+			 * scan keys are there for this attribute, so we simply
+			 * allocate the largest number possible. This may waste
+			 * a bit of memory, but we only expect small number of
+			 * scan keys in general, so this should be negligible,
+			 * and it's cheaper than having to repalloc repeatedly.
+			 */
+			keys[keyattno - 1] = palloc0(sizeof(ScanKey) * scan->numberOfKeys);
+
+			/* First time this column, so look up consistent function */
+			Assert(consistentFn[keyattno - 1].fn_oid == InvalidOid);
+
+			tmp = index_getprocinfo(idxRel, keyattno,
+									BRIN_PROCNUM_CONSISTENT);
+			fmgr_info_copy(&consistentFn[keyattno - 1], tmp,
+						   CurrentMemoryContext);
+		}
+
+		/* Add key to the per-attribute array. */
+		keys[keyattno - 1][nkeys[keyattno - 1]] = key;
+		nkeys[keyattno - 1]++;
+	}
+
 	/* allocate an initial in-memory tuple, out of the per-range memcxt */
 	dtup = brin_new_memtuple(bdesc);
 
@@ -471,7 +529,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 			}
 			else
 			{
-				int			keyno;
+				int		attno;
 
 				/*
 				 * Compare scan keys with summary values stored for the range.
@@ -481,51 +539,75 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * no keys.
 				 */
 				addrange = true;
-				for (keyno = 0; keyno < scan->numberOfKeys; keyno++)
+				for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++)
 				{
-					ScanKey		key = &scan->keyData[keyno];
-					AttrNumber	keyattno = key->sk_attno;
-					BrinValues *bval = &dtup->bt_columns[keyattno - 1];
+					BrinValues *bval;
 					Datum		add;
 
-					/*
-					 * The collation of the scan key must match the collation
-					 * used in the index column (but only if the search is not
-					 * IS NULL/ IS NOT NULL).  Otherwise we shouldn't be using
-					 * this index ...
-					 */
-					Assert((key->sk_flags & SK_ISNULL) ||
-						   (key->sk_collation ==
-							TupleDescAttr(bdesc->bd_tupdesc,
-										  keyattno - 1)->attcollation));
+					/* skip attributes without any san keys */
+					if (nkeys[attno - 1] == 0)
+						continue;
 
-					/* First time this column? look up consistent function */
-					if (consistentFn[keyattno - 1].fn_oid == InvalidOid)
-					{
-						FmgrInfo   *tmp;
+					bval = &dtup->bt_columns[attno - 1];
 
-						tmp = index_getprocinfo(idxRel, keyattno,
-												BRIN_PROCNUM_CONSISTENT);
-						fmgr_info_copy(&consistentFn[keyattno - 1], tmp,
-									   CurrentMemoryContext);
-					}
+					Assert((nkeys[attno - 1] > 0) &&
+						   (nkeys[attno - 1] <= scan->numberOfKeys));
 
 					/*
 					 * Check whether the scan key is consistent with the page
 					 * range values; if so, have the pages in the range added
 					 * to the output bitmap.
 					 *
-					 * When there are multiple scan keys, failure to meet the
-					 * criteria for a single one of them is enough to discard
-					 * the range as a whole, so break out of the loop as soon
-					 * as a false return value is obtained.
+					 * The opclass may or may not support processing of multiple
+					 * scan keys. We can determine that based on the number of
+					 * arguments - functions with extra parameter (number of scan
+					 * keys) do support this, otherwise we have to simply pass the
+					 * scan keys one by one,
 					 */
-					add = FunctionCall3Coll(&consistentFn[keyattno - 1],
-											key->sk_collation,
-											PointerGetDatum(bdesc),
-											PointerGetDatum(bval),
-											PointerGetDatum(key));
-					addrange = DatumGetBool(add);
+					if (consistentFn[attno - 1].fn_nargs >= 4)
+					{
+						Oid			collation;
+
+						/*
+						 * Collation from the first key (has to be the same for
+						 * all keys for the same attribue).
+						 */
+						collation = keys[attno - 1][0]->sk_collation;
+
+						/* Check all keys at once */
+						add = FunctionCall4Coll(&consistentFn[attno - 1],
+												collation,
+												PointerGetDatum(bdesc),
+												PointerGetDatum(bval),
+												PointerGetDatum(keys[attno - 1]),
+												Int32GetDatum(nkeys[attno - 1]));
+						addrange = DatumGetBool(add);
+					}
+					else
+					{
+						/*
+						 * Check keys one by one
+						 *
+						 * When there are multiple scan keys, failure to meet the
+						 * criteria for a single one of them is enough to discard
+						 * the range as a whole, so break out of the loop as soon
+						 * as a false return value is obtained.
+						 */
+						int			keyno;
+
+						for (keyno = 0; keyno < nkeys[attno - 1]; keyno++)
+						{
+							add = FunctionCall3Coll(&consistentFn[attno - 1],
+													keys[attno - 1][keyno]->sk_collation,
+													PointerGetDatum(bdesc),
+													PointerGetDatum(bval),
+													PointerGetDatum(keys[attno - 1][keyno]));
+							addrange = DatumGetBool(add);
+							if (!addrange)
+								break;
+						}
+					}
+
 					if (!addrange)
 						break;
 				}
diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 12e5bddd1f..215bc794d3 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno,
 										uint16 procnum);
 static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
 												 Oid subtype, uint16 strategynum);
+static bool inclusion_consistent_key(BrinDesc *bdesc, BrinValues *column,
+									 ScanKey key, Oid colloid);
 
 
 /*
@@ -258,53 +260,109 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 {
 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
-	ScanKey		key = (ScanKey) PG_GETARG_POINTER(2);
-	Oid			colloid = PG_GET_COLLATION(),
-				subtype;
-	Datum		unionval;
-	AttrNumber	attno;
-	Datum		query;
-	FmgrInfo   *finfo;
-	Datum		result;
-
-	Assert(key->sk_attno == column->bv_attno);
+	ScanKey	   *keys = (ScanKey *) PG_GETARG_POINTER(2);
+	int			nkeys = PG_GETARG_INT32(3);
+	Oid			colloid = PG_GET_COLLATION();
+	int			keyno;
+	bool		regular_keys = false;
 
-	/* Handle IS NULL/IS NOT NULL tests. */
-	if (key->sk_flags & SK_ISNULL)
+	/*
+	 * First check if there are any IS NULL scan keys, and if we're
+	 * violating them. In that case we can terminate early, without
+	 * inspecting the ranges.
+	 */
+	for (keyno = 0; keyno < nkeys; keyno++)
 	{
-		if (key->sk_flags & SK_SEARCHNULL)
+		ScanKey	key = keys[keyno];
+
+		Assert(key->sk_attno == column->bv_attno);
+
+		/* handle IS NULL/IS NOT NULL tests */
+		if (key->sk_flags & SK_ISNULL)
 		{
-			if (column->bv_allnulls || column->bv_hasnulls)
-				PG_RETURN_BOOL(true);
-			PG_RETURN_BOOL(false);
-		}
+			if (key->sk_flags & SK_SEARCHNULL)
+			{
+				if (column->bv_allnulls || column->bv_hasnulls)
+					continue;	/* this key is fine, continue */
 
-		/*
-		 * For IS NOT NULL, we can only skip ranges that are known to have
-		 * only nulls.
-		 */
-		if (key->sk_flags & SK_SEARCHNOTNULL)
-			PG_RETURN_BOOL(!column->bv_allnulls);
+				PG_RETURN_BOOL(false);
+			}
 
-		/*
-		 * Neither IS NULL nor IS NOT NULL was used; assume all indexable
-		 * operators are strict and return false.
-		 */
-		PG_RETURN_BOOL(false);
+			/*
+			 * For IS NOT NULL, we can only skip ranges that are known to have
+			 * only nulls.
+			 */
+			if (key->sk_flags & SK_SEARCHNOTNULL)
+			{
+				if (column->bv_allnulls)
+					PG_RETURN_BOOL(false);
+
+				continue;
+			}
+
+			/*
+			 * Neither IS NULL nor IS NOT NULL was used; assume all indexable
+			 * operators are strict and return false.
+			 */
+			PG_RETURN_BOOL(false);
+		}
+		else
+			/* note we have regular (non-NULL) scan keys */
+			regular_keys = true;
 	}
 
-	/* If it is all nulls, it cannot possibly be consistent. */
-	if (column->bv_allnulls)
+	/*
+	 * If the page range is all nulls, it cannot possibly be consistent if
+	 * there are some regular scan keys.
+	 */
+	if (column->bv_allnulls && regular_keys)
 		PG_RETURN_BOOL(false);
 
+	/* If there are no regular keys, the page range is considered consistent. */
+	if (!regular_keys)
+		PG_RETURN_BOOL(true);
+
 	/* It has to be checked, if it contains elements that are not mergeable. */
 	if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE]))
 		PG_RETURN_BOOL(true);
 
-	attno = key->sk_attno;
-	subtype = key->sk_subtype;
-	query = key->sk_argument;
-	unionval = column->bv_values[INCLUSION_UNION];
+	/* Check that the range is consistent with all scan keys. */
+	for (keyno = 0; keyno < nkeys; keyno++)
+	{
+		ScanKey		key = keys[keyno];
+
+		/* ignore IS NULL/IS NOT NULL tests handled above */
+		if (key->sk_flags & SK_ISNULL)
+			continue;
+
+		/*
+		 * When there are multiple scan keys, failure to meet the
+		 * criteria for a single one of them is enough to discard
+		 * the range as a whole, so break out of the loop as soon
+		 * as a false return value is obtained.
+		 */
+		if (!inclusion_consistent_key(bdesc, column, key, colloid))
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+/*
+ * inclusion_consistent_key
+ *		Determine if the range is consistent with a single scan key.
+ */
+static bool
+inclusion_consistent_key(BrinDesc *bdesc, BrinValues *column, ScanKey key,
+						 Oid colloid)
+{
+	FmgrInfo   *finfo;
+	AttrNumber	attno = key->sk_attno;
+	Oid			subtype = key->sk_subtype;
+	Datum		query = key->sk_argument;
+	Datum		unionval = column->bv_values[INCLUSION_UNION];
+	Datum		result;
+
 	switch (key->sk_strategy)
 	{
 			/*
@@ -324,49 +382,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTOverRightStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTOverLeftStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTRightStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTOverRightStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTLeftStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTRightStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTOverLeftStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTBelowStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTOverAboveStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTOverBelowStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTAboveStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTOverAboveStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTBelowStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		case RTAboveStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTOverBelowStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 			/*
 			 * Overlap and contains strategies
@@ -384,7 +442,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													key->sk_strategy);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_DATUM(result);
+			return DatumGetBool(result);
 
 			/*
 			 * Contained by strategies
@@ -404,9 +462,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 													RTOverlapStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
 			if (DatumGetBool(result))
-				PG_RETURN_BOOL(true);
+				return true;
 
-			PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
+			return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 			/*
 			 * Adjacent strategy
@@ -423,12 +481,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 													RTOverlapStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
 			if (DatumGetBool(result))
-				PG_RETURN_BOOL(true);
+				return true;
 
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
-													RTAdjacentStrategyNumber);
+														RTAdjacentStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_DATUM(result);
+			return DatumGetBool(result);
 
 			/*
 			 * Basic comparison strategies
@@ -458,9 +516,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 													RTRightStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
 			if (!DatumGetBool(result))
-				PG_RETURN_BOOL(true);
+				return true;
 
-			PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
+			return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTSameStrategyNumber:
 		case RTEqualStrategyNumber:
@@ -468,30 +526,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 													RTContainsStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
 			if (DatumGetBool(result))
-				PG_RETURN_BOOL(true);
+				return true;
 
-			PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
+			return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTGreaterEqualStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTLeftStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
 			if (!DatumGetBool(result))
-				PG_RETURN_BOOL(true);
+				return true;
 
-			PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
+			return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTGreaterStrategyNumber:
 			/* no need to check for empty elements */
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
 													RTLeftStrategyNumber);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_BOOL(!DatumGetBool(result));
+			return !DatumGetBool(result);
 
 		default:
 			/* shouldn't happen */
 			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-			PG_RETURN_BOOL(false);
+			return false;
 	}
 }
 
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 2ffbd9bf0d..12878ff3a0 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -30,6 +30,8 @@ typedef struct MinmaxOpaque
 
 static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
 											  Oid subtype, uint16 strategynum);
+static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column,
+								  ScanKey key, Oid colloid);
 
 
 Datum
@@ -146,47 +148,104 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 {
 	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
-	ScanKey		key = (ScanKey) PG_GETARG_POINTER(2);
-	Oid			colloid = PG_GET_COLLATION(),
-				subtype;
-	AttrNumber	attno;
-	Datum		value;
-	Datum		matches;
-	FmgrInfo   *finfo;
-
-	Assert(key->sk_attno == column->bv_attno);
+	ScanKey	   *keys = (ScanKey *) PG_GETARG_POINTER(2);
+	int			nkeys = PG_GETARG_INT32(3);
+	Oid			colloid = PG_GET_COLLATION();
+	int			keyno;
+	bool		regular_keys = false;
 
-	/* handle IS NULL/IS NOT NULL tests */
-	if (key->sk_flags & SK_ISNULL)
+	/*
+	 * First check if there are any IS NULL scan keys, and if we're
+	 * violating them. In that case we can terminate early, without
+	 * inspecting the ranges.
+	 */
+	for (keyno = 0; keyno < nkeys; keyno++)
 	{
-		if (key->sk_flags & SK_SEARCHNULL)
+		ScanKey	key = keys[keyno];
+
+		Assert(key->sk_attno == column->bv_attno);
+
+		/* handle IS NULL/IS NOT NULL tests */
+		if (key->sk_flags & SK_ISNULL)
 		{
-			if (column->bv_allnulls || column->bv_hasnulls)
-				PG_RETURN_BOOL(true);
+			if (key->sk_flags & SK_SEARCHNULL)
+			{
+				if (column->bv_allnulls || column->bv_hasnulls)
+					continue;	/* this key is fine, continue */
+
+				PG_RETURN_BOOL(false);
+			}
+
+			/*
+			 * For IS NOT NULL, we can only skip ranges that are known to have
+			 * only nulls.
+			 */
+			if (key->sk_flags & SK_SEARCHNOTNULL)
+			{
+				if (column->bv_allnulls)
+					PG_RETURN_BOOL(false);
+
+				continue;
+			}
+
+			/*
+			 * Neither IS NULL nor IS NOT NULL was used; assume all indexable
+			 * operators are strict and return false.
+			 */
 			PG_RETURN_BOOL(false);
 		}
+		else
+			/* note we have regular (non-NULL) scan keys */
+			regular_keys = true;
+	}
 
-		/*
-		 * For IS NOT NULL, we can only skip ranges that are known to have
-		 * only nulls.
-		 */
-		if (key->sk_flags & SK_SEARCHNOTNULL)
-			PG_RETURN_BOOL(!column->bv_allnulls);
+	/*
+	 * If the page range is all nulls, it cannot possibly be consistent if
+	 * there are some regular scan keys.
+	 */
+	if (column->bv_allnulls && regular_keys)
+		PG_RETURN_BOOL(false);
+
+	/* If there are no regular keys, the page range is considered consistent. */
+	if (!regular_keys)
+		PG_RETURN_BOOL(true);
 
-		/*
-		 * Neither IS NULL nor IS NOT NULL was used; assume all indexable
-		 * operators are strict and return false.
+	/* Check that the range is consistent with all scan keys. */
+	for (keyno = 0; keyno < nkeys; keyno++)
+	{
+		ScanKey	key = keys[keyno];
+
+		/* ignore IS NULL/IS NOT NULL tests handled above */
+		if (key->sk_flags & SK_ISNULL)
+			continue;
+
+		 /*
+		 * When there are multiple scan keys, failure to meet the
+		 * criteria for a single one of them is enough to discard
+		 * the range as a whole, so break out of the loop as soon
+		 * as a false return value is obtained.
 		 */
-		PG_RETURN_BOOL(false);
+		if (!minmax_consistent_key(bdesc, column, key, colloid))
+			PG_RETURN_DATUM(false);;
 	}
 
-	/* if the range is all empty, it cannot possibly be consistent */
-	if (column->bv_allnulls)
-		PG_RETURN_BOOL(false);
+	PG_RETURN_DATUM(true);
+}
+
+/*
+ * minmax_consistent_key
+ *		Determine if the range is consistent with a single scan key.
+ */
+static bool
+minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, ScanKey key,
+					  Oid colloid)
+{
+	FmgrInfo   *finfo;
+	AttrNumber	attno = key->sk_attno;
+	Oid			subtype = key->sk_subtype;
+	Datum		value = key->sk_argument;
+	Datum		matches;
 
-	attno = key->sk_attno;
-	subtype = key->sk_subtype;
-	value = key->sk_argument;
 	switch (key->sk_strategy)
 	{
 		case BTLessStrategyNumber:
@@ -229,7 +288,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 			break;
 	}
 
-	PG_RETURN_DATUM(matches);
+	return DatumGetBool(matches);
 }
 
 /*
diff --git a/src/backend/access/brin/brin_validate.c b/src/backend/access/brin/brin_validate.c
index 6d4253c05e..11835d85cd 100644
--- a/src/backend/access/brin/brin_validate.c
+++ b/src/backend/access/brin/brin_validate.c
@@ -97,8 +97,8 @@ brinvalidate(Oid opclassoid)
 				break;
 			case BRIN_PROCNUM_CONSISTENT:
 				ok = check_amproc_signature(procform->amproc, BOOLOID, true,
-											3, 3, INTERNALOID, INTERNALOID,
-											INTERNALOID);
+											3, 4, INTERNALOID, INTERNALOID,
+											INTERNALOID, INT4OID);
 				break;
 			case BRIN_PROCNUM_UNION:
 				ok = check_amproc_signature(procform->amproc, BOOLOID, true,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..4099e72001 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8191,7 +8191,7 @@
   prosrc => 'brin_minmax_add_value' },
 { oid => '3385', descr => 'BRIN minmax support',
   proname => 'brin_minmax_consistent', prorettype => 'bool',
-  proargtypes => 'internal internal internal',
+  proargtypes => 'internal internal internal int4',
   prosrc => 'brin_minmax_consistent' },
 { oid => '3386', descr => 'BRIN minmax support',
   proname => 'brin_minmax_union', prorettype => 'bool',
@@ -8207,7 +8207,7 @@
   prosrc => 'brin_inclusion_add_value' },
 { oid => '4107', descr => 'BRIN inclusion support',
   proname => 'brin_inclusion_consistent', prorettype => 'bool',
-  proargtypes => 'internal internal internal',
+  proargtypes => 'internal internal internal int4',
   prosrc => 'brin_inclusion_consistent' },
 { oid => '4108', descr => 'BRIN inclusion support',
   proname => 'brin_inclusion_union', prorettype => 'bool',
-- 
2.26.2


--------------4B194FF8F3EA3786FF9EAE1F
Content-Type: application/vnd.oasis.opendocument.spreadsheet;
 name="brin-bench.ods"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="brin-bench.ods"

UEsDBBQAAAgAAOO7Q1KFbDmKLgAAAC4AAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQu
b2FzaXMub3BlbmRvY3VtZW50LnNwcmVhZHNoZWV0UEsDBBQAAAgAAOO7Q1IAAAAAAAAAAAAA
AAAcAAAAQ29uZmlndXJhdGlvbnMyL2FjY2VsZXJhdG9yL1BLAwQUAAAIAADju0NSAAAAAAAA
AAAAAAAAHwAAAENvbmZpZ3VyYXRpb25zMi9pbWFnZXMvQml0bWFwcy9QSwMEFAAACAAA47tD
UgAAAAAAAAAAAAAAABgAAABDb25maWd1cmF0aW9uczIvdG9vbGJhci9QSwMEFAAACAAA47tD
UgAAAAAAAAAAAAAAABgAAABDb25maWd1cmF0aW9uczIvZmxvYXRlci9QSwMEFAAACAAA47tD
UgAAAAAAAAAAAAAAABoAAABDb25maWd1cmF0aW9uczIvcG9wdXBtZW51L1BLAwQUAAAIAADj
u0NSAAAAAAAAAAAAAAAAGAAAAENvbmZpZ3VyYXRpb25zMi9tZW51YmFyL1BLAwQUAAAIAADj
u0NSAAAAAAAAAAAAAAAAGgAAAENvbmZpZ3VyYXRpb25zMi90b29scGFuZWwvUEsDBBQAAAgA
AOO7Q1IAAAAAAAAAAAAAAAAaAAAAQ29uZmlndXJhdGlvbnMyL3N0YXR1c2Jhci9QSwMEFAAA
CAAA47tDUgAAAAAAAAAAAAAAABwAAABDb25maWd1cmF0aW9uczIvcHJvZ3Jlc3NiYXIvUEsD
BBQACAgIAOO7Q1IAAAAAAAAAAAAAAAAMAAAAbWFuaWZlc3QucmRmzZPNboMwEITvPIVlzthA
LwUFcijKuWqfwDWGWAUv8poS3r6Ok1ZRpKrqn9TjrkYz3460m+1hHMiLsqjBVDRjKSXKSGi1
6Ss6uy65pds62ti2Kx+aHfFqg6WfKrp3bio5X5aFLTcMbM+zoih4mvM8T7wiwdU4cUgMxrSO
CAkejUJp9eR8GjnO4glmV1F066CQefcgPYvdOqmgsgphtlK9h7YgkYFAjQlMyoR0gxy6TkvF
M5bzUTnBoe3ix2C904OiPGDwK47P2N6IDKblXuC9sO5cg998lWh67mN6ddPF8d8jlGCcMu5P
6rs7ef/n/i7P/xnir7R2RGxAzqNn+pDntPIfVUevUEsHCLT3aNIFAQAAgwMAAFBLAwQUAAgI
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
qj3ilyQOPhr0ABLtL3rhZyov0EILLbTQQgsttND+W1rqv9n/cfyPtLu9/1v//Op6vYEY/1mt
W6p3rm9s+rFGaz8x2MF6O6wN/SU0Y2o3tvDifH1Jv4qU7vqardljWAZb3pyj1Tq6spysn/fd
2kTWub1a2+VJvKrVtXjeoiYdYprGsV9fxoqz3RtNUinHrl0haYvWpK6ELiWP4mOcD1bNUlgn
6uNKr69UXetjCoGnpsaut8D1spZC5LSaHJ0d1czXz8RDc7d5Xl9JkXeLGk6noLyv069jXdNk
vHUxiHZKk/clm/mSVvMxB7N+Wwrb+ur8Rl5vV7WKquUlh2kYw5Hzcn1uJ5KV344XmvjGZUpE
Stoq+wVtm/1QsoBjNO341mdqLcsKuza3xemjv4aNbm0aIxcZr6vahaTTlXfDKXC0/hPnbbIh
dLuFtK3JJRedK9LWwkv4dazpvPpsU+lmWzg6OQPk79IYL24UG9bslrl36/rubP6q+62s5Pf9
wHc1PnlNpg9l0CfL+PFzjx2XGxkfGnh7uNz5iO/djLTU957HpfBBL+pD0eqTRd9D7o/6e7y+
FvihAf0zWF0OlqCfvepL4Vohc/xLuiQb8JEHU5ZKK8zje+XvT6sPycePmnz4Ht4L/c7BjvxQ
okqyG1PpV7RJyfVaJ3XoNLfogiEllSAVaYLorhsnctwrZOQlenur3985r7+vxDrOVa7h5t79
g2aV7QOJ5jA3Zp3MfkE78spr5nXxXL0erGVDloocPvFtUd4Zmr3e9SJbgfZzf8nBurVNy3Qo
ye30uaENTd1KTi2JhPxyNutjV2NFaXsgTNKB5lK9tJLLyr5x1OeWrIjnTVJcK7mhyCW72Fil
RVKxeefJNrbtTDT4diYsRe0z70dtfGYwi2RAEm5b7UfOj82tm6eHlvLEeapzuaI957ZIJuBl
gM4pTefcynGfv+dXGCXispx2WSvuXdu8BEn6K6f8lk5tdzJYkhnObZP1/Nj3xHPwzGNa+Sog
s/Zr7b7Z3nVVh6qmhiXLeVvlkNdkzlZiYLWrbqpApuj7RLfd5Dy/acdUJWViard6qGJL9HmW
JnV9bG7H1LflgTDpfshB1wvavEn22sPOR5AlmSP9rHDmvp8P8kI+nxX5WC2UIh2V25Hlo+fZ
MrZt2+/VEs/L1vHg3PIeHhmmXbofb0eC/RZaaKGFFlpooYUWWmihhRZaaKGFFlpooYUWWmih
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
--------------4B194FF8F3EA3786FF9EAE1F
Content-Type: application/x-compressed-tar;
 name="brin-stress-test.tgz"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="brin-stress-test.tgz"

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
4sSJE/vk/y10LU4AkAEA
--------------4B194FF8F3EA3786FF9EAE1F
Content-Type: application/x-compressed-tar;
 name="brin-bench.tgz"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="brin-bench.tgz"

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
--------------4B194FF8F3EA3786FF9EAE1F--





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* ExecRTCheckPerms() and many prunable partitions
@ 2021-06-30 13:33  Amit Langote <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Amit Langote @ 2021-06-30 13:33 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

Hi,

Last year in [1], I had briefly mentioned $subject.  I'm starting this
thread to propose a small patch to alleviate the inefficiency of that
case.

As also mentioned in [1], when running -Mprepared benchmarks
(plan_cache_mode = force_generic_plan) using partitioned tables,
ExecRTCheckPerms() tends to show up in the profile, especially with
large partition counts.  Granted it's lurking behind
AcquireExecutorLocks(), LockReleaseAll() et al, but still seems like a
problem we should do something about.

The problem is that it loops over the entire range table even though
only one or handful of those entries actually need their permissions
checked.  Most entries, especially those of partition child tables
have their requiredPerms set to 0, which David pointed out to me in
[2], so what ExecCheckRTPerms() does in their case is pure overhead.

An idea to fix that is to store the RT indexes of the entries that
have non-0 requiredPerms into a separate list or a bitmapset in
PlannedStmt.  I thought of two implementation ideas for how to set
that:

1. Put add_rtes_to_flat_rtable() in the charge of populating it:

@@ -324,12 +324,18 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
     * flattened rangetable match up with their original indexes.  When
     * recursing, we only care about extracting relation RTEs.
     */
+   rti = 1;
    foreach(lc, root->parse->rtable)
    {
        RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);

        if (!recursing || rte->rtekind == RTE_RELATION)
+       {
            add_rte_to_flat_rtable(glob, rte);
+           if (rte->requiredPerms != 0)
+               glob->checkPermRels = bms_add_member(glob->checkPermRels, rti);
+       }
+       rti++
    }

2. Start populating checkPermRels in ParseState (parse_relation.c),
passing it along in Query through the rewriter and finally the
planner.

1 seems very simple, but appears to add overhead to what is likely a
oft-taken path.  Also, the newly added code would have to run as many
times as there are partitions, which sounds like a dealbreaker to me.

2 can seem a bit complex.  Given that the set is tracked in Query,
special care is needed to handle views and subqueries correctly,
because those features involve intricate manipulation of Query nodes
and their range tables.  However, most of that special care code
remains out of the busy paths.  Also, none of that code touches
partition/child RTEs, so unaffected by how many of them there are.

For now, I have implemented the idea 2 as the attached patch.  While
it passes make check-world, I am not fully confident yet that it
correctly handles all the cases involving views and subqueries.

So while still kind of PoC, will add this to July CF for keeping track.

-- 
Amit Langote
EDB: http://www.enterprisedb.com

[1] https://www.postgresql.org/message-id/[email protected]...
[2] https://www.postgresql.org/message-id/CAApHDvqPzsMcKLRpmNpUW97PmaQDTmD7b2BayEPS5AN4LY-0bA%40mail.gma...


Attachments:

  [application/octet-stream] 0001-Explicitly-track-RT-indexes-of-relations-to-check-pe.patch (20.0K, ../../CA+HiwqGjJDmUhDSfv-U2qhKJjt9ST7Xh9JXC_irsAQ1TAUsJYg@mail.gmail.com/2-0001-Explicitly-track-RT-indexes-of-relations-to-check-pe.patch)
  download | inline diff:
From 6d429165eda81f1c8e1dc1781e5b571788293dcc Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 30 Jun 2021 20:08:25 +0900
Subject: [PATCH] Explicitly track RT indexes of relations to check permissions
 of

---
 src/backend/commands/copy.c               |  2 +-
 src/backend/executor/execMain.c           | 14 ++++++++------
 src/backend/executor/execParallel.c       |  1 +
 src/backend/nodes/copyfuncs.c             |  2 ++
 src/backend/nodes/equalfuncs.c            |  1 +
 src/backend/nodes/outfuncs.c              |  2 ++
 src/backend/nodes/readfuncs.c             |  2 ++
 src/backend/optimizer/plan/planner.c      |  1 +
 src/backend/optimizer/plan/setrefs.c      | 12 ++++++++++++
 src/backend/optimizer/prep/prepjointree.c |  2 ++
 src/backend/parser/analyze.c              |  8 ++++++++
 src/backend/parser/parse_relation.c       | 11 +++++++++++
 src/backend/parser/parse_utilcmd.c        |  1 +
 src/backend/rewrite/rewriteHandler.c      | 12 ++++++++++++
 src/backend/rewrite/rewriteManip.c        |  2 ++
 src/backend/utils/adt/ri_triggers.c       |  4 +++-
 src/include/executor/executor.h           |  3 ++-
 src/include/nodes/parsenodes.h            |  2 ++
 src/include/nodes/plannodes.h             |  3 +++
 src/include/parser/parse_node.h           |  2 ++
 20 files changed, 78 insertions(+), 9 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8265b981eb..500dc51b15 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -158,7 +158,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			else
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckRTPerms(pstate->p_rtable, bms_make_singleton(1), true);
 
 		/*
 		 * Permission check for row security policies.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..8ed6dd6235 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -553,7 +553,7 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 /*
  * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ *		Check access permissions for the specified relations in a range table.
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,14 +565,16 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckRTPerms(List *rangeTable, Bitmapset *checkPermRels,
+				 bool ereport_on_violation)
 {
-	ListCell   *l;
+	int			rti;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	rti = -1;
+	while ((rti = bms_next_member(checkPermRels, rti)) > 0)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RangeTblEntry *rte = (RangeTblEntry *) list_nth(rangeTable, rti - 1);
 
 		result = ExecCheckRTEPerms(rte);
 		if (!result)
@@ -815,7 +817,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	/*
 	 * Do permissions checks
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckRTPerms(rangeTable, plannedstmt->checkPermRels, true);
 
 	/*
 	 * initialize the node's execution state
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 12c41d746b..21971f1e10 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->checkPermRels = NULL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bd87f23784..f9241f56ca 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -90,6 +90,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_BITMAPSET_FIELD(checkPermRels);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -3176,6 +3177,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_BITMAPSET_FIELD(checkPermRels);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index dba3e6b31e..0313b2a469 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -979,6 +979,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_BITMAPSET_FIELD(checkPermRels);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e32b92e299..7bee62dc75 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -308,6 +308,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_BITMAPSET_FIELD(checkPermRels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -3071,6 +3072,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_BITMAPSET_FIELD(checkPermRels);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..f0f80b5ac4 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_BITMAPSET_FIELD(checkPermRels);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1589,6 +1590,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_BITMAPSET_FIELD(checkPermRels);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..ce8484fe21 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -513,6 +513,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->checkPermRels = parse->checkPermRels;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..592de92aba 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -360,6 +360,8 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 
 			if (rel != NULL)
 			{
+				int		rtoff = list_length(glob->finalrtable);
+
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
@@ -387,6 +389,16 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
 													  UPPERREL_FINAL, NULL)))
 					add_rtes_to_flat_rtable(rel->subroot, true);
+
+				/*
+				 * Pull up the checkPermRels set of subqueries that themselves
+				 * were not.
+				 */
+				Assert(rte->subquery != NULL);
+				root->parse->checkPermRels =
+					bms_union(root->parse->checkPermRels,
+							  offset_relid_set(rte->subquery->checkPermRels,
+											   rtoff));
 			}
 		}
 		rti++;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 62a1668796..ec057e1335 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1132,6 +1132,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	parse->checkPermRels = bms_union(parse->checkPermRels, subquery->checkPermRels);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 438b077004..d698191b0c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -475,6 +475,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -895,6 +896,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1348,6 +1350,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1582,6 +1585,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1828,6 +1832,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2286,6 +2291,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2352,6 +2358,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2711,6 +2718,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->checkPermRels= pstate->p_check_perm_rels;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7465919044..7479e2c5a2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1464,6 +1464,8 @@ addRangeTableEntry(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	pstate->p_check_perm_rels = bms_add_member(pstate->p_check_perm_rels,
+										   list_length(pstate->p_rtable));
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -1552,6 +1554,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	pstate->p_check_perm_rels = bms_add_member(pstate->p_check_perm_rels,
+										   list_length(pstate->p_rtable));
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -1649,6 +1653,7 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -1955,6 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -2026,6 +2032,7 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -2113,6 +2120,7 @@ addRangeTableEntryForValues(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -2204,6 +2212,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -2354,6 +2363,7 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
@@ -2477,6 +2487,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+	/* No need to add this RTE's index to pstate->p_check_perm_rels. */
 
 	/*
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 81d3e7990c..65385d310a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3069,6 +3069,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->checkPermRels = pstate->p_check_perm_rels;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 88a9e95e33..1b393d5710 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -420,6 +420,8 @@ rewriteRuleAction(Query *parsetree,
 	 */
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
+	sub_action->checkPermRels = bms_union(parsetree->checkPermRels,
+									   sub_action->checkPermRels);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1720,6 +1722,9 @@ ApplyRetrieveRule(Query *parsetree,
 			rte = rt_fetch(rt_index, parsetree->rtable);
 			newrte = copyObject(rte);
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
+			parsetree->checkPermRels =
+				bms_add_member(parsetree->checkPermRels,
+							   list_length(parsetree->rtable));
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
 			/*
@@ -1842,6 +1847,11 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->updatedCols = NULL;
 	rte->extraUpdatedCols = NULL;
 
+	/* Update checkPermRels set in the respective Query nodes. */
+	parsetree->checkPermRels = bms_del_member(parsetree->checkPermRels, rt_index);
+	rule_action->checkPermRels = bms_add_member(rule_action->checkPermRels,
+											 PRS2_OLD_VARNO);
+
 	return parsetree;
 }
 
@@ -3195,6 +3205,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	parsetree->rtable = lappend(parsetree->rtable, new_rte);
 	new_rt_index = list_length(parsetree->rtable);
+	parsetree->checkPermRels = bms_add_member(parsetree->checkPermRels,
+										   new_rt_index);
 
 	/*
 	 * INSERTs never inherit.  For UPDATE/DELETE, we use the view query's
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index d4e0b8b4de..0a0f5f2d5c 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -461,6 +461,8 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
 
 				rc->rti += offset;
 			}
+
+			qry->checkPermRels = offset_relid_set(qry->checkPermRels, offset);
 		}
 		query_tree_walker(qry, OffsetVarNodes_walker,
 						  (void *) &context, 0);
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..83536afa23 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1352,7 +1352,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte),
+						  bms_add_member(bms_make_singleton(1), 2),
+						  false))
 		return false;
 
 	/*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3dc03c913e..0e679b66b5 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckRTPerms(List *rangeTable, Bitmapset *checkPermRels,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..a27fd30093 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -145,6 +145,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	Bitmapset  *checkPermRels;	/* range table indexes of relations to check
+								 * the permissions of */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaa3b65d04..1b90c38034 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -65,6 +65,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	Bitmapset  *checkPermRels;	/* range table indexes of relations to check
+								 * the permissions of */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1500de2dd0..6b93988eb9 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	Bitmapset  *p_check_perm_rels;	/* range table indexes of relations to
+									 * check the permissions of */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-06-30 14:33  David Rowley <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: David Rowley @ 2021-06-30 14:33 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Thu, 1 Jul 2021 at 01:34, Amit Langote <[email protected]> wrote:
> For now, I have implemented the idea 2 as the attached patch.

I only just had a fleeting glance at the patch. Aren't you
accidentally missing the 0th RTE here?

+ while ((rti = bms_next_member(checkPermRels, rti)) > 0)
  {
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ RangeTblEntry *rte = (RangeTblEntry *) list_nth(rangeTable, rti - 1);

I'd have expected >= 0 rather than > 0.

David





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-06-30 14:58  Amit Langote <[email protected]>
  parent: David Rowley <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2021-06-30 14:58 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Wed, Jun 30, 2021 at 23:34 David Rowley <[email protected]> wrote:

> On Thu, 1 Jul 2021 at 01:34, Amit Langote <[email protected]> wrote:
> > For now, I have implemented the idea 2 as the attached patch.
>
> I only just had a fleeting glance at the patch. Aren't you
> accidentally missing the 0th RTE here?
>
> + while ((rti = bms_next_member(checkPermRels, rti)) > 0)
>   {
> - RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
> + RangeTblEntry *rte = (RangeTblEntry *) list_nth(rangeTable, rti - 1);
>
> I'd have expected >= 0 rather than > 0.


Hmm, a valid RT index cannot be 0, so that seems fine to me.  Note that RT
indexes are added as-is to that bitmapset, not after subtracting 1.

> --
Amit Langote
EDB: http://www.enterprisedb.com


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-06-30 22:51  David Rowley <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: David Rowley @ 2021-06-30 22:51 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Thu, 1 Jul 2021 at 02:58, Amit Langote <[email protected]> wrote:
>
> On Wed, Jun 30, 2021 at 23:34 David Rowley <[email protected]> wrote:
>> + while ((rti = bms_next_member(checkPermRels, rti)) > 0)
>>   {
>> - RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
>> + RangeTblEntry *rte = (RangeTblEntry *) list_nth(rangeTable, rti - 1);
>>
>> I'd have expected >= 0 rather than > 0.
>
> Hmm, a valid RT index cannot be 0, so that seems fine to me.  Note that RT indexes are added as-is to that bitmapset, not after subtracting 1.

Oh, you're right. My mistake.

David





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-07-01 15:45  Tom Lane <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Tom Lane @ 2021-07-01 15:45 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Amit Langote <[email protected]> writes:
> The problem is that it loops over the entire range table even though
> only one or handful of those entries actually need their permissions
> checked.  Most entries, especially those of partition child tables
> have their requiredPerms set to 0, which David pointed out to me in
> [2], so what ExecCheckRTPerms() does in their case is pure overhead.

> An idea to fix that is to store the RT indexes of the entries that
> have non-0 requiredPerms into a separate list or a bitmapset in
> PlannedStmt.

I think perhaps we ought to be more ambitious than that, and consider
separating the list of permissions-to-check from the rtable entirely.
Your patch hardly qualifies as non-invasive, plus it seems to invite
errors of omission, while if we changed the data structure altogether
then the compiler would help find any not-updated code.

But the main reason that this strikes me as possibly a good idea
is that I was just taking another look at the complaint in [1],
where I wrote

>> I think it's impossible to avoid less-than-O(N^2) growth on this sort
>> of case.  For example, the v2 subquery initially has RTEs for v2 itself
>> plus v1.  When we flatten v1 into v2, v2 acquires the RTEs from v1,
>> namely v1 itself plus foo.  Similarly, once vK-1 is pulled up into vK,
>> there are going to be order-of-K entries in vK's rtable, and that stacking
>> makes for O(N^2) work overall just in manipulating the rtable.
>> 
>> We can't get rid of these rtable entries altogether, since all of them
>> represent table privilege checks that the executor will need to do.

Perhaps, if we separated the rtable from the required-permissions data
structure, then we could avoid pulling up otherwise-useless RTEs when
flattening a view (or even better, not make the extra RTEs in the
first place??), and thus possibly avoid that exponential planning-time
growth for nested views.

Or maybe not.  But I think we should take a hard look at whether
separating these data structures could solve both of these problems
at once.

			regards, tom lane

[1] https://www.postgresql.org/message-id/flat/797aff54-b49b-4914-9ff9-aa42564a4d7d%40www.fastmail.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-07-02 00:40  Amit Langote <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Amit Langote @ 2021-07-02 00:40 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Fri, Jul 2, 2021 at 12:45 AM Tom Lane <[email protected]> wrote:
> Amit Langote <[email protected]> writes:
> > The problem is that it loops over the entire range table even though
> > only one or handful of those entries actually need their permissions
> > checked.  Most entries, especially those of partition child tables
> > have their requiredPerms set to 0, which David pointed out to me in
> > [2], so what ExecCheckRTPerms() does in their case is pure overhead.
>
> > An idea to fix that is to store the RT indexes of the entries that
> > have non-0 requiredPerms into a separate list or a bitmapset in
> > PlannedStmt.
>
> I think perhaps we ought to be more ambitious than that, and consider
> separating the list of permissions-to-check from the rtable entirely.
> Your patch hardly qualifies as non-invasive, plus it seems to invite
> errors of omission, while if we changed the data structure altogether
> then the compiler would help find any not-updated code.
>
> But the main reason that this strikes me as possibly a good idea
> is that I was just taking another look at the complaint in [1],
> where I wrote
>
> >> I think it's impossible to avoid less-than-O(N^2) growth on this sort
> >> of case.  For example, the v2 subquery initially has RTEs for v2 itself
> >> plus v1.  When we flatten v1 into v2, v2 acquires the RTEs from v1,
> >> namely v1 itself plus foo.  Similarly, once vK-1 is pulled up into vK,
> >> there are going to be order-of-K entries in vK's rtable, and that stacking
> >> makes for O(N^2) work overall just in manipulating the rtable.
> >>
> >> We can't get rid of these rtable entries altogether, since all of them
> >> represent table privilege checks that the executor will need to do.
>
> Perhaps, if we separated the rtable from the required-permissions data
> structure, then we could avoid pulling up otherwise-useless RTEs when
> flattening a view (or even better, not make the extra RTEs in the
> first place??), and thus possibly avoid that exponential planning-time
> growth for nested views.
>
> Or maybe not.  But I think we should take a hard look at whether
> separating these data structures could solve both of these problems
> at once.

Ah, okay.  I'll think about decoupling the permission checking stuff
from the range table data structure.

Thanks for the feedback.

I'll mark the CF entry as WoA, unless you'd rather I just mark it RwF.

-- 
Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-07-07 04:41  David Rowley <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: David Rowley @ 2021-07-07 04:41 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, 2 Jul 2021 at 12:41, Amit Langote <[email protected]> wrote:
> I'll mark the CF entry as WoA, unless you'd rather I just mark it RwF.

I've set it to waiting on author. It was still set to needs review.

If you think you'll not get time to write the patch during this CF,
feel free to bump it out.

David





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-07-07 07:12  Amit Langote <[email protected]>
  parent: David Rowley <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Amit Langote @ 2021-07-07 07:12 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Jul 7, 2021 at 1:41 PM David Rowley <[email protected]> wrote:
> On Fri, 2 Jul 2021 at 12:41, Amit Langote <[email protected]> wrote:
> > I'll mark the CF entry as WoA, unless you'd rather I just mark it RwF.
>
> I've set it to waiting on author. It was still set to needs review.

Sorry it slipped my mind to do that and thanks.

> If you think you'll not get time to write the patch during this CF,
> feel free to bump it out.

I will try to post an update next week if not later this week,
hopefully with an updated patch.

-- 
Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-07-29 08:40  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Amit Langote @ 2021-07-29 08:40 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Fri, Jul 2, 2021 at 9:40 AM Amit Langote <[email protected]> wrote:
> On Fri, Jul 2, 2021 at 12:45 AM Tom Lane <[email protected]> wrote:
> > I think perhaps we ought to be more ambitious than that, and consider
> > separating the list of permissions-to-check from the rtable entirely.
> > Your patch hardly qualifies as non-invasive, plus it seems to invite
> > errors of omission, while if we changed the data structure altogether
> > then the compiler would help find any not-updated code.
> >
> > But the main reason that this strikes me as possibly a good idea
> > is that I was just taking another look at the complaint in [1],
> > where I wrote
> >
> > >> I think it's impossible to avoid less-than-O(N^2) growth on this sort
> > >> of case.  For example, the v2 subquery initially has RTEs for v2 itself
> > >> plus v1.  When we flatten v1 into v2, v2 acquires the RTEs from v1,
> > >> namely v1 itself plus foo.  Similarly, once vK-1 is pulled up into vK,
> > >> there are going to be order-of-K entries in vK's rtable, and that stacking
> > >> makes for O(N^2) work overall just in manipulating the rtable.
> > >>
> > >> We can't get rid of these rtable entries altogether, since all of them
> > >> represent table privilege checks that the executor will need to do.
> >
> > Perhaps, if we separated the rtable from the required-permissions data
> > structure, then we could avoid pulling up otherwise-useless RTEs when
> > flattening a view (or even better, not make the extra RTEs in the
> > first place??), and thus possibly avoid that exponential planning-time
> > growth for nested views.
> >
> > Or maybe not.  But I think we should take a hard look at whether
> > separating these data structures could solve both of these problems
> > at once.
>
> Ah, okay.  I'll think about decoupling the permission checking stuff
> from the range table data structure.

I have finished with the attached.  Sorry about the delay.

Think I've managed to get the first part done -- getting the
permission-checking info out of the range table -- but have not
seriously attempted the second -- doing away with the OLD/NEW range
table entries in the view/rule action queries, assuming that is what
you meant in the quoted.

One design point I think might need reconsidering is that the list of
the new RelPermissionInfo nodes that holds the permission-checking
info for relations has to be looked up with a linear search using the
relation OID, whereas it was basically free before if a particular of
code had the RTE handy.  Maybe I need to check if the overhead of that
is noticeable in some cases.

As there's not much time left in this CF, I've bumped the entry to the next one.

-- 
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v2-0001-Rework-query-relation-permission-checking.patch (139.7K, ../../CA+HiwqFfiai=qBxPDTjaio_ZcaqUKh+FC=prESrB8ogZgFNNNQ@mail.gmail.com/2-v2-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 8db404d3c91a64573153a223878092b5a1eaac1f Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v2] Rework query relation permission checking

Currently, any information about the permissions to be checked is
stored in query's range table entries.  Only the permissions of
RTE_RELATION entries need be checked, that too only for the relations
that are directly mentioned in the query, not those added afterwards,
say, due to expanding inheritance.  This arrangement means that the
executor, which actually checks the permissions, must wade through
the range table to find those entries that need their permissions
checked, which can be severely wasteful when there are many entries
in it, say, due to many partitions being added.

This commit moves the permission checking information out of the
range table entries into a new node type called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table, keyed on relation OIDs.

The list is initialized by the parser when initializing the range
table.  The rewriter can add more entries to it as rules/views are
expanded.  Finally, the planner combines the lists of the individual
subqueries into one flat list that is passed down to the executor.
---
 contrib/postgres_fdw/postgres_fdw.c       |  75 +++++---
 contrib/sepgsql/dml.c                     |  42 ++--
 contrib/sepgsql/hooks.c                   |   6 +-
 src/backend/access/common/attmap.c        |  13 +-
 src/backend/access/common/tupconvert.c    |   2 +-
 src/backend/catalog/partition.c           |   3 +-
 src/backend/commands/copy.c               |  18 +-
 src/backend/commands/copyfrom.c           |   9 +
 src/backend/commands/indexcmds.c          |   3 +-
 src/backend/commands/tablecmds.c          |  24 ++-
 src/backend/commands/view.c               |   4 -
 src/backend/executor/execMain.c           | 105 +++++-----
 src/backend/executor/execParallel.c       |   1 +
 src/backend/executor/execPartition.c      |  12 +-
 src/backend/executor/execUtils.c          | 137 +++++++++----
 src/backend/nodes/copyfuncs.c             |  31 ++-
 src/backend/nodes/equalfuncs.c            |  16 +-
 src/backend/nodes/outfuncs.c              |  28 ++-
 src/backend/nodes/readfuncs.c             |  22 ++-
 src/backend/optimizer/plan/createplan.c   |   6 +-
 src/backend/optimizer/plan/planner.c      |   6 +
 src/backend/optimizer/plan/setrefs.c      |   8 +-
 src/backend/optimizer/plan/subselect.c    |   2 +
 src/backend/optimizer/prep/prepjointree.c |  26 +++
 src/backend/optimizer/util/inherit.c      | 169 +++++++++++-----
 src/backend/optimizer/util/relnode.c      |   9 +-
 src/backend/parser/analyze.c              |  61 ++++--
 src/backend/parser/parse_clause.c         |   9 +-
 src/backend/parser/parse_relation.c       | 223 ++++++++++++++--------
 src/backend/parser/parse_target.c         |  19 +-
 src/backend/parser/parse_utilcmd.c        |   9 +-
 src/backend/replication/logical/worker.c  |  13 +-
 src/backend/rewrite/rewriteDefine.c       |  15 +-
 src/backend/rewrite/rewriteHandler.c      | 178 ++++++++---------
 src/backend/rewrite/rowsecurity.c         |  24 ++-
 src/backend/statistics/extended_stats.c   |   7 +-
 src/backend/utils/adt/ri_triggers.c       |  34 ++--
 src/backend/utils/adt/selfuncs.c          |  19 +-
 src/include/access/attmap.h               |   6 +-
 src/include/commands/copyfrom_internal.h  |   3 +-
 src/include/executor/executor.h           |   7 +-
 src/include/nodes/execnodes.h             |   9 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/parsenodes.h            |  81 ++++----
 src/include/nodes/pathnodes.h             |   5 +-
 src/include/nodes/plannodes.h             |   5 +
 src/include/optimizer/inherit.h           |   1 +
 src/include/optimizer/planner.h           |   1 +
 src/include/parser/parse_node.h           |   6 +-
 src/include/parser/parse_relation.h       |   3 +
 src/include/rewrite/rewriteHandler.h      |   2 +-
 51 files changed, 970 insertions(+), 548 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 51fac77f3d..00e0b7ee37 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -457,7 +459,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -621,7 +624,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -655,12 +657,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermisssions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1513,16 +1515,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermisssions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1802,7 +1803,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1881,6 +1883,29 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RelPermissionInfo *perminfo;
+	Oid		relid;
+	Oid		result;
+
+	if (relInfo->ri_RootResultRelInfo)
+		relid = RelationGetRelid(relInfo->ri_RootResultRelInfo->ri_RelationDesc);
+	else
+		relid = RelationGetRelid(relInfo->ri_RelationDesc);
+
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, relid, false);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1892,6 +1917,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1899,6 +1925,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1922,6 +1949,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1933,7 +1961,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2125,6 +2154,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2202,6 +2232,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2218,7 +2250,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2604,7 +2637,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2624,13 +2656,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3928,12 +3959,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3945,12 +3976,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 1f96e8b507..44ec89840f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 19a3ffb7ff..036a4c97f2 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 32405f8610..85221ada52 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 64f54393f3..f5624eeab9 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 790f4ccb92..ffc45efb32 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b33951e0c..5118343f02 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..4e9e94eee0 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -654,6 +654,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1384,7 +1390,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..75e4b0cbf3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1230,7 +1230,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fcd778c62a..63889d8875 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1173,7 +1173,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9506,7 +9507,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9648,7 +9650,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9834,7 +9837,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	table_close(pg_constraint, RowShareLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -9981,7 +9985,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -11890,7 +11895,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -17573,7 +17579,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18459,7 +18466,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..5bfa730e8a 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -381,10 +381,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..5bde876b78 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -73,7 +73,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -89,8 +89,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -552,8 +552,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,38 +565,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -604,32 +605,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermisssions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -663,14 +653,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -695,15 +685,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -711,12 +701,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -771,17 +761,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -813,9 +800,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1773,7 +1761,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1858,7 +1846,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1910,7 +1899,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2017,7 +2007,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f8a4a40e7b..6c932b8261 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..f4456a1aca 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -574,7 +574,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -631,7 +632,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -773,7 +775,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -1040,7 +1043,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 6ef37c0886..7e649be151 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,32 +1252,76 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
+{
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
+	{
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
+
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
 /* Return a bitmap representing columns being inserted */
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
+	 * The columns are stored in estate->relpermlist.  If this ResultRelInfo
+	 * represents a child relation (a partition routing target of an INSERT or
+	 * a child UPDATE target), it doesn't have an entry of its own, so fetch
+	 * the parent's entry and map the columns to the order they are in the
+	 * partition.
 	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->insertedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
+		else
+			return perminfo->insertedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		return perminfo->insertedCols;
 	}
 	else
 	{
@@ -1295,22 +1340,28 @@ Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->updatedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
+		else
+			return perminfo->updatedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		return perminfo->updatedCols;
 	}
 	else
 		return NULL;
@@ -1321,22 +1372,28 @@ Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
+		else
+			return perminfo->extraUpdatedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		return perminfo->extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 29020c908e..2f8864db6c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -90,6 +90,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -776,6 +777,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1276,6 +1278,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2492,12 +2513,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3177,6 +3192,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5118,6 +5134,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..7603c04784 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -985,6 +985,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -2755,17 +2756,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -3793,6 +3802,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 48202d2232..53319bc215 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -308,6 +308,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -702,6 +703,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -988,6 +990,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2263,6 +2280,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3072,6 +3090,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3304,12 +3323,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -3991,6 +4004,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 77d082d8b4..7269b6c5e4 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1505,13 +1506,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_INT_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
+	READ_INT_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1590,6 +1602,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2075,6 +2088,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -2847,6 +2861,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index d3f8639a40..888ea416d4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4051,6 +4051,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5698,7 +5701,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 86816ffe19..644139011f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -299,6 +300,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -486,6 +488,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -513,6 +516,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5772,6 +5776,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, tableOid);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -5899,6 +5904,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, tableOid);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b145c5f45f..2a0876c6c9 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -260,6 +260,10 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 */
 	add_rtes_to_flat_rtable(root, false);
 
+	/* Also add the query's RelPermissionInfos to the global list. */
+	glob->finalrelpermlist = list_concat(glob->finalrelpermlist,
+										 root->parse->relpermlist);
+
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
 	 */
@@ -438,9 +442,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..5a622cabe5 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1504,6 +1504,8 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+												 subselect->relpermlist);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 224c5153b1..b1a1eec17f 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1131,6 +1131,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+												 subquery->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1268,6 +1271,9 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 * Append child RTEs to parent rtable.
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->relpermlist =
+		MergeRelPermissionInfos(root->parse->relpermlist,
+								subquery->relpermlist);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
@@ -1311,6 +1317,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
+		RangeTblEntry *rte = rt_fetch(rtr->rtindex, root->parse->rtable);
 		int			childRTindex;
 		AppendRelInfo *appinfo;
 
@@ -1345,6 +1352,25 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
 		rtr->rtindex = childRTindex;
 		(void) pull_up_subqueries_recurse(root, (Node *) rtr,
 										  NULL, NULL, appinfo);
+
+		/*
+		 * If the subquery was not pulled up, need to merge its relpermlist
+		 * entries into the parent query by ourselves.  It's a no-op if the
+		 * subquery is not a simple one, because its relpermlist would be
+		 * empty in that case.
+		 *
+		 * HACK: this relies on rte->subquery still being non-NULL to know
+		 * that the subquery pull-up failed; it's set to NULL by
+		 * pull_up_simple_subquery() when it succeeds.
+		 */
+		if (rte->rtekind == RTE_SUBQUERY && rte->subquery)
+		{
+			Query   *subquery = rte->subquery;
+
+			root->parse->relpermlist =
+				MergeRelPermissionInfos(root->parse->relpermlist,
+										subquery->relpermlist);
+		}
 	}
 	else if (IsA(setOp, SetOperationStmt))
 	{
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 992ef87b9d..84a7941889 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte->relid, false);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +472,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +495,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +557,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +856,84 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist,
+									rte->relid,
+									false);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index e105a4d5f1..d07c233aad 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* otherrels use the root parent's value. */
+		rel->userid = parent ? parent->userid :
+			GetRelPermissionInfo(root->parse->relpermlist,
+								 rte->relid, false)->checkAsUser;
+	}
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 438b077004..4586d71548 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -475,6 +475,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -507,7 +508,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -626,6 +627,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -851,7 +858,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -867,8 +874,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -895,6 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1053,8 +1061,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1348,6 +1354,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1582,6 +1589,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1828,6 +1836,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2286,6 +2295,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2352,6 +2362,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2370,7 +2381,7 @@ static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2382,7 +2393,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2423,8 +2434,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2711,6 +2722,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3180,9 +3192,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist,
+														rte->relid, false);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3232,9 +3252,18 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist,
+														 rte->relid, false);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index b3f151d33b..d5537c424a 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3242,16 +3242,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7465919044..9ecbbb09f7 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1010,10 +1010,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte->relid, false);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1224,10 +1227,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1263,6 +1269,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1406,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1442,7 +1450,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize acesss permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1451,12 +1459,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte->relid);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1470,7 +1480,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1507,6 +1517,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo = NULL;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1530,7 +1541,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1539,12 +1550,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte->relid);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1558,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1628,21 +1641,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1935,20 +1942,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1960,7 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2006,20 +2006,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2094,19 +2087,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2185,19 +2172,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,6 +2193,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2335,19 +2317,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2461,16 +2437,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2482,7 +2455,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3102,6 +3075,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3119,7 +3093,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3665,3 +3642,93 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation OID and adds it into
+ *		the provided list unless added already
+ *
+ * Returns the RelPermssionInfo.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, Oid relid)
+{
+	RelPermissionInfo *perminfo;
+
+	/* Don't allow duplicate entries for a given relation. */
+	perminfo = GetRelPermissionInfo(*relpermlist, relid, true);
+	if (perminfo)
+		return perminfo;
+
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Tries to find a RelPermissionInfo for given relation OID in the
+ *		provided list, erroring out or returning NULL (depending on
+ *		missing_ok) if not found
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, Oid relid, bool missing_ok)
+{
+	ListCell *lc;
+
+	Assert(OidIsValid(relid));
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo	*perminfo = lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfo in the 'from' list to the 'into' list,
+ *		"merging" the contents of any that are present in both.
+ *
+ * Returns the destination list.
+ */
+List *
+MergeRelPermissionInfos(List *into, List *from)
+{
+	ListCell *l;
+
+	foreach(l, from)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *target;
+
+		target = AddRelPermissionInfo(&into, perminfo->relid);
+
+		/* "merge" proprties. */
+		target->inh = perminfo->inh;
+		target->requiredPerms |= perminfo->requiredPerms;
+		if (!OidIsValid(target->checkAsUser))
+			target->checkAsUser = perminfo->checkAsUser;
+		target->selectedCols = bms_union(target->selectedCols,
+										 perminfo->selectedCols);
+		target->insertedCols = bms_union(target->insertedCols,
+										 perminfo->insertedCols);
+		target->updatedCols = bms_union(target->updatedCols,
+										perminfo->updatedCols);
+		target->extraUpdatedCols = bms_union(target->extraUpdatedCols,
+											 perminfo->extraUpdatedCols);
+	}
+
+	return into;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6e8fbc4780..6d308b2cd8 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1142,7 +1142,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 675e400839..1af71073cd 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3010,9 +3011,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3068,6 +3066,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3110,8 +3109,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index b9a7a7ffbb..ef78cf1ca6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -155,6 +155,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -462,6 +463,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte->relid);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1617,7 +1620,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	if (handle_streamed_transaction(LOGICAL_REP_MSG_UPDATE, s))
@@ -1657,7 +1660,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1667,14 +1670,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..5ffcb58306 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -821,18 +822,20 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 54fd6d6fb2..92462daa61 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,25 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	sub_action->relpermlist =
+		MergeRelPermissionInfos(copyObject(parsetree->relpermlist),
+								sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1566,16 +1569,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1593,9 +1598,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1680,8 +1685,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1722,18 +1726,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1822,26 +1814,6 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->tablesample = NULL;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1870,8 +1842,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist,
+											rte->relid, false);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3012,6 +2989,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3147,6 +3127,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist,
+										 base_rte->relid,
+										 false);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3219,51 +3202,53 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * Mark the new target RTE for the permissions checks that we want to
+	 * Mark the new target relation for the permissions checks that we want to
 	 * enforce against the view owner, as distinct from the query caller.  At
 	 * the relation level, require the same INSERT/UPDATE/DELETE permissions
-	 * that the query caller needs against the view.  We drop the ACL_SELECT
-	 * bit that is presumably in new_rte->requiredPerms initially.
+	 * that the query caller needs against the view.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
-	new_rte->checkAsUser = view->rd_rel->relowner;
-	new_rte->requiredPerms = view_rte->requiredPerms;
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist,
+										 view_rte->relid, false);
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist,
+										new_rte->relid);
+	new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3367,7 +3352,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3378,8 +3363,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3653,6 +3636,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3664,6 +3648,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist,
+										   rt_entry->relid, false);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3749,7 +3735,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_DELETE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e10f94904e..9da976e375 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte->relid, false);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -241,7 +247,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * ALL or SELECT USING policy.
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -284,7 +290,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -340,7 +346,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -369,7 +375,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -386,8 +392,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 2e55913bc8..f438b71229 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -30,6 +30,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1555,6 +1556,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1602,10 +1604,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..486fedfd43 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1308,8 +1308,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1327,32 +1327,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1362,9 +1356,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..15bb356572 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5135,7 +5135,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ?
+									onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5187,7 +5188,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ?
+											onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5271,7 +5273,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ?
+						onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5319,7 +5322,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ?
+								onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5380,6 +5384,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5388,7 +5393,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ?
+				onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5457,7 +5463,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ?
+					onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 778fa27fd1..f3ce859395 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 4d68d9cceb..752e204ec7 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3dc03c913e..19ed90b956 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -598,6 +599,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 37cb4f3d59..5d44c88238 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -522,6 +522,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 } ResultRelInfo;
@@ -563,6 +571,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f7b009ec43..e7a346ac42 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e28248af32..e89ce12998 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -145,6 +145,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
@@ -934,37 +936,6 @@ typedef struct PartitionCmd
  *	  needed by ruleutils.c to determine whether RTEs should be shown in
  *	  decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1142,14 +1113,58 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e20c245f98..2154ac42e3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -100,6 +100,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat list of RelPermissionInfo "*/
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -725,7 +727,8 @@ typedef struct RelOptInfo
 
 	/* Information about foreign tables and foreign joins */
 	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
+	Oid			userid;			/* identifies user to check access as; set
+								 * in non-foreign table relations too! */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 98a4c73f93..b92b730b7d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
+#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -65,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -633,6 +637,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index e9472f2f73..1ec96d89bd 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 9a15de5025..5b884ad96f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
 #endif							/* PLANNER_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1500de2dd0..dd4b751f73 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -233,7 +235,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -267,6 +270,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..ae06487670 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,8 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist, Oid relid);
+extern List *MergeRelPermissionInfos(List *into, List *from);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist, Oid relid, bool missing_ok);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 728a60c0b0..26300cc143 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-08-20 13:46  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2021-08-20 13:46 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Thu, Jul 29, 2021 at 5:40 PM Amit Langote <[email protected]> wrote:
> On Fri, Jul 2, 2021 at 9:40 AM Amit Langote <[email protected]> wrote:
> > On Fri, Jul 2, 2021 at 12:45 AM Tom Lane <[email protected]> wrote:
> > > Perhaps, if we separated the rtable from the required-permissions data
> > > structure, then we could avoid pulling up otherwise-useless RTEs when
> > > flattening a view (or even better, not make the extra RTEs in the
> > > first place??), and thus possibly avoid that exponential planning-time
> > > growth for nested views.
>
> Think I've managed to get the first part done -- getting the
> permission-checking info out of the range table -- but have not
> seriously attempted the second -- doing away with the OLD/NEW range
> table entries in the view/rule action queries, assuming that is what
> you meant in the quoted.

I took a stab at the 2nd part, implemented in the attached 0002.

The patch removes UpdateRangeTableOfViewParse() which would add the
dummy OLD/NEW entries to a view rule's action query's rtable, citing
these reasons:

- * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.

0001 makes them unnecessary for permission checking.  Though, a
RELATION-kind RTE still be must be present in the rtable for run-time
locking, so I adjusted ApplyRetrieveRule() as follows:

@@ -1803,16 +1804,26 @@ ApplyRetrieveRule(Query *parsetree,
     * original RTE to a subquery RTE.
     */
    rte = rt_fetch(rt_index, parsetree->rtable);
+   subquery_rte = rte;

-   rte->rtekind = RTE_SUBQUERY;
-   rte->subquery = rule_action;
-   rte->security_barrier = RelationIsSecurityView(relation);
+   /*
+    * Before modifying, store a copy of itself so as to serve as the entry
+    * to be used by the executor to lock the view relation and for the
+    * planner to be able to record the view relation OID in the PlannedStmt
+    * that it produces for the query.
+    */
+   rte = copyObject(rte);
+   parsetree->rtable = lappend(parsetree->rtable, rte);
+
+   subquery_rte->rtekind = RTE_SUBQUERY;
+   subquery_rte->subquery = rule_action;
+   subquery_rte->security_barrier = RelationIsSecurityView(relation);
    /* Clear fields that should not be set in a subquery RTE */
-   rte->relid = InvalidOid;
-   rte->relkind = 0;
-   rte->rellockmode = 0;
-   rte->tablesample = NULL;
-   rte->inh = false;           /* must not be set for a subquery */
+   subquery_rte->relid = InvalidOid;
+   subquery_rte->relkind = 0;
+   subquery_rte->rellockmode = 0;
+   subquery_rte->tablesample = NULL;
+   subquery_rte->inh = false;          /* must not be set for a subquery */

    return parsetree;
 }

Outputs for a bunch of regression tests needed to be adjusted to
account for that pg_get_viewdef() no longer qualifies view column
names in the deparsed queries, that is, if they reference only a
single relation.   Previously, those dummy OLD/NEW entries tricked
make_ruledef(), get_query_def() et al into setting
deparse_context.varprefix to true.  contrib/postgre_fdw test output
likewise needed adjustment due to its deparse code being impacted by
those dummy entries no longer being present, I believe.

I haven't yet checked how this further improves the performance for
the case discussed at [1] that prompted this.

-- 
Amit Langote
EDB: http://www.enterprisedb.com

[1] https://www.postgresql.org/message-id/flat/797aff54-b49b-4914-9ff9-aa42564a4d7d%40www.fastmail.com


Attachments:

  [application/octet-stream] v3-0002-Remove-UpdateRangeTableOfViewParse.patch (111.6K, ../../CA+HiwqGrsE1K4ABhbgB__nog2OL-bYZ0SLYzZtrtfbNeKHEHJQ@mail.gmail.com/2-v3-0002-Remove-UpdateRangeTableOfViewParse.patch)
  download | inline diff:
From 62f63bc271483050a7a38f12698102a28956ea15 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v3 2/2] Remove UpdateRangeTableOfViewParse()

---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  29 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  12 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 778 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 21 files changed, 722 insertions(+), 789 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e3ee30f1aa..5ed3c155cd 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2478,7 +2478,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2541,7 +2541,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6346,10 +6346,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6437,10 +6437,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 5bfa730e8a..e596683f27 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -323,78 +323,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -557,12 +485,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 92462daa61..1002f1dc11 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1685,7 +1685,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1803,16 +1804,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..03bc29a046 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2081,7 +2081,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2097,7 +2097,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2113,7 +2113,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2129,7 +2129,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2147,7 +2147,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -2952,7 +2952,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 5949996ebc..57df8f7889 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1535,7 +1535,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1587,7 +1587,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1603,7 +1603,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1619,7 +1619,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2104,15 +2104,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8dcb00ac67..8c2f75f29f 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2414,8 +2414,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2426,8 +2426,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2453,8 +2453,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2466,8 +2466,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f06ae543e4..5f4c5c0db6 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f50ef76685..dee7928ba2 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -341,10 +341,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -386,9 +386,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -402,9 +402,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -418,9 +418,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -434,9 +434,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -451,9 +451,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -467,9 +467,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -483,9 +483,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -499,9 +499,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -516,9 +516,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -532,9 +532,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -548,9 +548,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -564,9 +564,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -581,9 +581,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -597,9 +597,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -613,9 +613,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -629,9 +629,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -647,9 +647,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -663,9 +663,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -679,9 +679,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -695,9 +695,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -714,9 +714,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -730,9 +730,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -746,9 +746,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -762,9 +762,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1251,10 +1251,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1531,9 +1531,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1550,9 +1550,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1573,9 +1573,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1665,8 +1665,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1895,7 +1895,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -1947,19 +1947,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 7b6b0bb4f9..2b85967c1f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -179,12 +179,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 4c467c1b15..71d6ec7034 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index b75afcc01a..493ad70db8 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -633,10 +633,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -648,10 +648,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -666,10 +666,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -680,10 +680,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index cafca1f9ae..6347c62774 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..fefad636ca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1291,21 +1291,21 @@ iexit| SELECT ih.name,
    FROM ihighway ih,
     ramp r
   WHERE (ih.thepath ## r.thepath);
-key_dependent_view| SELECT view_base_table.key,
-    view_base_table.data
+key_dependent_view| SELECT key,
+    data
    FROM view_base_table
-  GROUP BY view_base_table.key;
+  GROUP BY key;
 key_dependent_view_no_cols| SELECT
    FROM view_base_table
-  GROUP BY view_base_table.key
- HAVING (length((view_base_table.data)::text) > 0);
-mvtest_tv| SELECT mvtest_t.type,
-    sum(mvtest_t.amt) AS totamt
+  GROUP BY key
+ HAVING (length((data)::text) > 0);
+mvtest_tv| SELECT type,
+    sum(amt) AS totamt
    FROM mvtest_t
-  GROUP BY mvtest_t.type;
-mvtest_tvv| SELECT sum(mvtest_tv.totamt) AS grandtot
+  GROUP BY type;
+mvtest_tvv| SELECT sum(totamt) AS grandtot
    FROM mvtest_tv;
-mvtest_tvvmv| SELECT mvtest_tvvm.grandtot
+mvtest_tvvmv| SELECT grandtot
    FROM mvtest_tvvm;
 pg_available_extension_versions| SELECT e.name,
     e.version,
@@ -1324,50 +1324,50 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1380,22 +1380,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1435,13 +1435,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1459,10 +1459,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1708,23 +1708,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1738,10 +1738,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1809,13 +1809,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1828,57 +1828,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1901,8 +1901,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1911,13 +1911,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2094,41 +2094,41 @@ pg_stat_subscription| SELECT su.oid AS subid,
     st.latest_end_time
    FROM (pg_subscription su
      LEFT JOIN pg_stat_get_subscription(NULL::oid) st(subid, relid, pid, received_lsn, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time) ON ((st.subid = su.oid)));
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2138,68 +2138,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2216,19 +2216,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2238,19 +2238,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2289,64 +2289,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname, t.oid, x.indexrelid;
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2529,24 +2529,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -2572,26 +2572,26 @@ pg_views| SELECT n.nspname AS schemaname,
    FROM (pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = 'v'::"char");
-rtest_v1| SELECT rtest_t1.a,
-    rtest_t1.b
+rtest_v1| SELECT a,
+    b
    FROM rtest_t1;
 rtest_vcomp| SELECT x.part,
     (x.size * y.factor) AS size_in_cm
    FROM rtest_comp x,
     rtest_unitfact y
   WHERE (x.unit = y.unit);
-rtest_vview1| SELECT x.a,
-    x.b
+rtest_vview1| SELECT a,
+    b
    FROM rtest_view1 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
           WHERE (y.a = x.a)));
-rtest_vview2| SELECT rtest_view1.a,
-    rtest_view1.b
+rtest_vview2| SELECT a,
+    b
    FROM rtest_view1
-  WHERE rtest_view1.v;
-rtest_vview3| SELECT x.a,
-    x.b
+  WHERE v;
+rtest_vview3| SELECT a,
+    b
    FROM rtest_vview2 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
@@ -2603,9 +2603,9 @@ rtest_vview4| SELECT x.a,
     rtest_view2 y
   WHERE (x.a = y.a)
   GROUP BY x.a, x.b;
-rtest_vview5| SELECT rtest_view1.a,
-    rtest_view1.b,
-    rtest_viewfunc1(rtest_view1.a) AS refcount
+rtest_vview5| SELECT a,
+    b,
+    rtest_viewfunc1(a) AS refcount
    FROM rtest_view1;
 shoe| SELECT sh.shoename,
     sh.sh_avail,
@@ -2635,20 +2635,20 @@ shoelace| SELECT s.sl_name,
    FROM shoelace_data s,
     unit u
   WHERE (s.sl_unit = u.un_name);
-shoelace_candelete| SELECT shoelace_obsolete.sl_name,
-    shoelace_obsolete.sl_avail,
-    shoelace_obsolete.sl_color,
-    shoelace_obsolete.sl_len,
-    shoelace_obsolete.sl_unit,
-    shoelace_obsolete.sl_len_cm
+shoelace_candelete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace_obsolete
-  WHERE (shoelace_obsolete.sl_avail = 0);
-shoelace_obsolete| SELECT shoelace.sl_name,
-    shoelace.sl_avail,
-    shoelace.sl_color,
-    shoelace.sl_len,
-    shoelace.sl_unit,
-    shoelace.sl_len_cm
+  WHERE (sl_avail = 0);
+shoelace_obsolete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace
   WHERE (NOT (EXISTS ( SELECT shoe.shoename
            FROM shoe
@@ -2659,14 +2659,14 @@ street| SELECT r.name,
    FROM ONLY road r,
     real_city c
   WHERE (c.outline ## r.thepath);
-test_tablesample_v1| SELECT test_tablesample.id
+test_tablesample_v1| SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
-test_tablesample_v2| SELECT test_tablesample.id
+test_tablesample_v2| SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
-toyemp| SELECT emp.name,
-    emp.age,
-    emp.location,
-    (12 * emp.salary) AS annualsal
+toyemp| SELECT name,
+    age,
+    location,
+    (12 * salary) AS annualsal
    FROM emp;
 SELECT tablename, rulename, definition FROM pg_rules
 WHERE schemaname IN ('pg_catalog', 'public')
@@ -3256,7 +3256,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3295,8 +3295,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3308,8 +3308,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3321,8 +3321,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3334,8 +3334,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 078358d226..15a64aca0c 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5d124cf96f..ed0f85e113 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1251,8 +1251,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..a026946fb4 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1666,19 +1666,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1719,17 +1719,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1759,17 +1759,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -1800,16 +1800,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -1831,15 +1831,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index bb9ff7f07b..5e4612fff1 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 3523a7dcc1..d262a7944e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -802,9 +802,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1291,9 +1291,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1313,9 +1313,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
-- 
2.24.1



  [application/octet-stream] v3-0001-Rework-query-relation-permission-checking.patch (139.8K, ../../CA+HiwqGrsE1K4ABhbgB__nog2OL-bYZ0SLYzZtrtfbNeKHEHJQ@mail.gmail.com/3-v3-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From f24664b587479893cb8ea44e714c981b85bcabf1 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v3 1/2] Rework query relation permission checking

Currently, any information about the permissions to be checked is
stored in query's range table entries.  Only the permissions of
RTE_RELATION entries need be checked, that too only for the relations
that are directly mentioned in the query, not those added afterwards,
say, due to expanding inheritance.  This arrangement means that the
executor, which actually checks the permissions, must wade through
the range table to find those entries that need their permissions
checked, which can be severely wasteful when there are many entries
in it, say, due to many partitions being added.

This commit moves the permission checking information out of the
range table entries into a new node type called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table, keyed on relation OIDs.

The list is initialized by the parser when initializing the range
table.  The rewriter can add more entries to it as rules/views are
expanded.  Finally, the planner combines the lists of the individual
subqueries into one flat list that is passed down to the executor.
---
 contrib/postgres_fdw/postgres_fdw.c       |  75 +++++---
 contrib/sepgsql/dml.c                     |  42 ++--
 contrib/sepgsql/hooks.c                   |   6 +-
 src/backend/access/common/attmap.c        |  13 +-
 src/backend/access/common/tupconvert.c    |   2 +-
 src/backend/catalog/partition.c           |   3 +-
 src/backend/commands/copy.c               |  18 +-
 src/backend/commands/copyfrom.c           |   9 +
 src/backend/commands/indexcmds.c          |   3 +-
 src/backend/commands/tablecmds.c          |  24 ++-
 src/backend/commands/view.c               |   4 -
 src/backend/executor/execMain.c           | 105 +++++-----
 src/backend/executor/execParallel.c       |   1 +
 src/backend/executor/execPartition.c      |  12 +-
 src/backend/executor/execUtils.c          | 137 +++++++++----
 src/backend/nodes/copyfuncs.c             |  31 ++-
 src/backend/nodes/equalfuncs.c            |  16 +-
 src/backend/nodes/outfuncs.c              |  28 ++-
 src/backend/nodes/readfuncs.c             |  22 ++-
 src/backend/optimizer/plan/createplan.c   |   6 +-
 src/backend/optimizer/plan/planner.c      |   6 +
 src/backend/optimizer/plan/setrefs.c      |   8 +-
 src/backend/optimizer/plan/subselect.c    |   2 +
 src/backend/optimizer/prep/prepjointree.c |  26 +++
 src/backend/optimizer/util/inherit.c      | 169 +++++++++++-----
 src/backend/optimizer/util/relnode.c      |   9 +-
 src/backend/parser/analyze.c              |  61 ++++--
 src/backend/parser/parse_clause.c         |   9 +-
 src/backend/parser/parse_relation.c       | 223 ++++++++++++++--------
 src/backend/parser/parse_target.c         |  19 +-
 src/backend/parser/parse_utilcmd.c        |   9 +-
 src/backend/replication/logical/worker.c  |  13 +-
 src/backend/rewrite/rewriteDefine.c       |  15 +-
 src/backend/rewrite/rewriteHandler.c      | 178 ++++++++---------
 src/backend/rewrite/rowsecurity.c         |  24 ++-
 src/backend/statistics/extended_stats.c   |   7 +-
 src/backend/utils/adt/ri_triggers.c       |  34 ++--
 src/backend/utils/adt/selfuncs.c          |  19 +-
 src/include/access/attmap.h               |   6 +-
 src/include/commands/copyfrom_internal.h  |   3 +-
 src/include/executor/executor.h           |   7 +-
 src/include/nodes/execnodes.h             |   9 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/parsenodes.h            |  81 ++++----
 src/include/nodes/pathnodes.h             |   5 +-
 src/include/nodes/plannodes.h             |   5 +
 src/include/optimizer/inherit.h           |   1 +
 src/include/optimizer/planner.h           |   1 +
 src/include/parser/parse_node.h           |   6 +-
 src/include/parser/parse_relation.h       |   3 +
 src/include/rewrite/rewriteHandler.h      |   2 +-
 51 files changed, 970 insertions(+), 548 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9d443baf02..39d28df3b6 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -457,7 +459,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -622,7 +625,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -656,12 +658,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermisssions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1514,16 +1516,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermisssions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1803,7 +1804,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1882,6 +1884,29 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RelPermissionInfo *perminfo;
+	Oid		relid;
+	Oid		result;
+
+	if (relInfo->ri_RootResultRelInfo)
+		relid = RelationGetRelid(relInfo->ri_RootResultRelInfo->ri_RelationDesc);
+	else
+		relid = RelationGetRelid(relInfo->ri_RelationDesc);
+
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, relid, false);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1893,6 +1918,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1900,6 +1926,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1923,6 +1950,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1934,7 +1962,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2126,6 +2155,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2203,6 +2233,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2219,7 +2251,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2605,7 +2638,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2625,13 +2657,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3929,12 +3960,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3946,12 +3977,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 1f96e8b507..44ec89840f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 19a3ffb7ff..036a4c97f2 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 32405f8610..85221ada52 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 64f54393f3..f5624eeab9 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 790f4ccb92..ffc45efb32 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b33951e0c..5118343f02 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..4e9e94eee0 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -654,6 +654,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1384,7 +1390,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..75e4b0cbf3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1230,7 +1230,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a03077139d..d46c89cfbf 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1172,7 +1172,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9463,7 +9464,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9605,7 +9607,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9791,7 +9794,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	table_close(pg_constraint, RowShareLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -9938,7 +9942,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -11847,7 +11852,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -17530,7 +17536,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18416,7 +18423,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..5bfa730e8a 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -381,10 +381,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..5bde876b78 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -73,7 +73,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -89,8 +89,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -552,8 +552,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,38 +565,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -604,32 +605,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermisssions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -663,14 +653,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -695,15 +685,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -711,12 +701,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -771,17 +761,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -813,9 +800,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1773,7 +1761,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1858,7 +1846,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1910,7 +1899,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2017,7 +2007,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f8a4a40e7b..6c932b8261 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..f4456a1aca 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -574,7 +574,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -631,7 +632,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -773,7 +775,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -1040,7 +1043,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 6ef37c0886..7e649be151 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,32 +1252,76 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
+{
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
+	{
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
+
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
 /* Return a bitmap representing columns being inserted */
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
+	 * The columns are stored in estate->relpermlist.  If this ResultRelInfo
+	 * represents a child relation (a partition routing target of an INSERT or
+	 * a child UPDATE target), it doesn't have an entry of its own, so fetch
+	 * the parent's entry and map the columns to the order they are in the
+	 * partition.
 	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->insertedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
+		else
+			return perminfo->insertedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		return perminfo->insertedCols;
 	}
 	else
 	{
@@ -1295,22 +1340,28 @@ Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->updatedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
+		else
+			return perminfo->updatedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		return perminfo->updatedCols;
 	}
 	else
 		return NULL;
@@ -1321,22 +1372,28 @@ Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
+		else
+			return perminfo->extraUpdatedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		return perminfo->extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..db56906d17 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -776,6 +777,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1264,6 +1266,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2480,12 +2501,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3165,6 +3180,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5106,6 +5122,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..7603c04784 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -985,6 +985,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -2755,17 +2756,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -3793,6 +3802,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 87561cbb6f..2d3c239e13 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -308,6 +308,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -702,6 +703,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -988,6 +990,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2263,6 +2280,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3073,6 +3091,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3305,12 +3324,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -3992,6 +4005,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0dd1ad7dfc..d8e30b8dbb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1505,13 +1506,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_INT_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
+	READ_INT_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1590,6 +1602,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2075,6 +2088,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -2847,6 +2861,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index a5f6d678cc..700643d2c4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4051,6 +4051,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5698,7 +5701,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2cd691191c..485f78da93 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -299,6 +300,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -486,6 +488,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -513,6 +516,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5772,6 +5776,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, tableOid);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -5899,6 +5904,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, tableOid);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e50624c465..b519c53f70 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -260,6 +260,10 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 */
 	add_rtes_to_flat_rtable(root, false);
 
+	/* Also add the query's RelPermissionInfos to the global list. */
+	glob->finalrelpermlist = list_concat(glob->finalrelpermlist,
+										 root->parse->relpermlist);
+
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
 	 */
@@ -438,9 +442,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..5a622cabe5 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1504,6 +1504,8 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+												 subselect->relpermlist);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 224c5153b1..b1a1eec17f 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1131,6 +1131,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+												 subquery->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1268,6 +1271,9 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 * Append child RTEs to parent rtable.
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->relpermlist =
+		MergeRelPermissionInfos(root->parse->relpermlist,
+								subquery->relpermlist);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
@@ -1311,6 +1317,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
+		RangeTblEntry *rte = rt_fetch(rtr->rtindex, root->parse->rtable);
 		int			childRTindex;
 		AppendRelInfo *appinfo;
 
@@ -1345,6 +1352,25 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
 		rtr->rtindex = childRTindex;
 		(void) pull_up_subqueries_recurse(root, (Node *) rtr,
 										  NULL, NULL, appinfo);
+
+		/*
+		 * If the subquery was not pulled up, need to merge its relpermlist
+		 * entries into the parent query by ourselves.  It's a no-op if the
+		 * subquery is not a simple one, because its relpermlist would be
+		 * empty in that case.
+		 *
+		 * HACK: this relies on rte->subquery still being non-NULL to know
+		 * that the subquery pull-up failed; it's set to NULL by
+		 * pull_up_simple_subquery() when it succeeds.
+		 */
+		if (rte->rtekind == RTE_SUBQUERY && rte->subquery)
+		{
+			Query   *subquery = rte->subquery;
+
+			root->parse->relpermlist =
+				MergeRelPermissionInfos(root->parse->relpermlist,
+										subquery->relpermlist);
+		}
 	}
 	else if (IsA(setOp, SetOperationStmt))
 	{
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index c758172efa..282c754cc6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte->relid, false);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +472,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +495,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +557,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +856,84 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist,
+									rte->relid,
+									false);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 47769cea45..6c7eeeb419 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* otherrels use the root parent's value. */
+		rel->userid = parent ? parent->userid :
+			GetRelPermissionInfo(root->parse->relpermlist,
+								 rte->relid, false)->checkAsUser;
+	}
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 15669c8238..2d929f097b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -475,6 +475,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -507,7 +508,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -626,6 +627,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -851,7 +858,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -867,8 +874,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -895,6 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1053,8 +1061,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1348,6 +1354,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1582,6 +1589,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1828,6 +1836,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2286,6 +2295,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2352,6 +2362,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2370,7 +2381,7 @@ static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2382,7 +2393,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2423,8 +2434,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2711,6 +2722,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3189,9 +3201,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist,
+														rte->relid, false);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3247,9 +3267,18 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist,
+														 rte->relid, false);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index b3f151d33b..d5537c424a 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3242,16 +3242,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7465919044..9ecbbb09f7 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1010,10 +1010,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte->relid, false);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1224,10 +1227,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1263,6 +1269,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1406,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1442,7 +1450,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize acesss permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1451,12 +1459,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte->relid);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1470,7 +1480,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1507,6 +1517,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo = NULL;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1530,7 +1541,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1539,12 +1550,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte->relid);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1558,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1628,21 +1641,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1935,20 +1942,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1960,7 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2006,20 +2006,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2094,19 +2087,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2185,19 +2172,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,6 +2193,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2335,19 +2317,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2461,16 +2437,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2482,7 +2455,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3102,6 +3075,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3119,7 +3093,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3665,3 +3642,93 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation OID and adds it into
+ *		the provided list unless added already
+ *
+ * Returns the RelPermssionInfo.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, Oid relid)
+{
+	RelPermissionInfo *perminfo;
+
+	/* Don't allow duplicate entries for a given relation. */
+	perminfo = GetRelPermissionInfo(*relpermlist, relid, true);
+	if (perminfo)
+		return perminfo;
+
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Tries to find a RelPermissionInfo for given relation OID in the
+ *		provided list, erroring out or returning NULL (depending on
+ *		missing_ok) if not found
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, Oid relid, bool missing_ok)
+{
+	ListCell *lc;
+
+	Assert(OidIsValid(relid));
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo	*perminfo = lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfo in the 'from' list to the 'into' list,
+ *		"merging" the contents of any that are present in both.
+ *
+ * Returns the destination list.
+ */
+List *
+MergeRelPermissionInfos(List *into, List *from)
+{
+	ListCell *l;
+
+	foreach(l, from)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *target;
+
+		target = AddRelPermissionInfo(&into, perminfo->relid);
+
+		/* "merge" proprties. */
+		target->inh = perminfo->inh;
+		target->requiredPerms |= perminfo->requiredPerms;
+		if (!OidIsValid(target->checkAsUser))
+			target->checkAsUser = perminfo->checkAsUser;
+		target->selectedCols = bms_union(target->selectedCols,
+										 perminfo->selectedCols);
+		target->insertedCols = bms_union(target->insertedCols,
+										 perminfo->insertedCols);
+		target->updatedCols = bms_union(target->updatedCols,
+										perminfo->updatedCols);
+		target->extraUpdatedCols = bms_union(target->extraUpdatedCols,
+											 perminfo->extraUpdatedCols);
+	}
+
+	return into;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6e8fbc4780..6d308b2cd8 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1142,7 +1142,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 675e400839..1af71073cd 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3010,9 +3011,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3068,6 +3066,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3110,8 +3109,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 38b493e4f5..db1b1f1002 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -155,6 +155,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -463,6 +464,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte->relid);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1691,7 +1694,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	if (handle_streamed_transaction(LOGICAL_REP_MSG_UPDATE, s))
@@ -1731,7 +1734,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1741,14 +1744,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..5ffcb58306 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -821,18 +822,20 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 54fd6d6fb2..92462daa61 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,25 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	sub_action->relpermlist =
+		MergeRelPermissionInfos(copyObject(parsetree->relpermlist),
+								sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1566,16 +1569,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1593,9 +1598,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1680,8 +1685,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1722,18 +1726,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1822,26 +1814,6 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->tablesample = NULL;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1870,8 +1842,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist,
+											rte->relid, false);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3012,6 +2989,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3147,6 +3127,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist,
+										 base_rte->relid,
+										 false);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3219,51 +3202,53 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * Mark the new target RTE for the permissions checks that we want to
+	 * Mark the new target relation for the permissions checks that we want to
 	 * enforce against the view owner, as distinct from the query caller.  At
 	 * the relation level, require the same INSERT/UPDATE/DELETE permissions
-	 * that the query caller needs against the view.  We drop the ACL_SELECT
-	 * bit that is presumably in new_rte->requiredPerms initially.
+	 * that the query caller needs against the view.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
-	new_rte->checkAsUser = view->rd_rel->relowner;
-	new_rte->requiredPerms = view_rte->requiredPerms;
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist,
+										 view_rte->relid, false);
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist,
+										new_rte->relid);
+	new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3367,7 +3352,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3378,8 +3363,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3653,6 +3636,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3664,6 +3648,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist,
+										   rt_entry->relid, false);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3749,7 +3735,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_DELETE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e10f94904e..9da976e375 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte->relid, false);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -241,7 +247,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * ALL or SELECT USING policy.
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -284,7 +290,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -340,7 +346,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -369,7 +375,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -386,8 +392,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 2e55913bc8..f438b71229 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -30,6 +30,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1555,6 +1556,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1602,10 +1604,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..486fedfd43 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1308,8 +1308,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1327,32 +1327,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1362,9 +1356,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..15bb356572 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5135,7 +5135,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ?
+									onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5187,7 +5188,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ?
+											onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5271,7 +5273,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ?
+						onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5319,7 +5322,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ?
+								onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5380,6 +5384,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5388,7 +5393,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ?
+				onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5457,7 +5463,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ?
+					onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 778fa27fd1..f3ce859395 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 4d68d9cceb..752e204ec7 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3dc03c913e..19ed90b956 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -598,6 +599,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 37cb4f3d59..5d44c88238 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -522,6 +522,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 } ResultRelInfo;
@@ -563,6 +571,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..ee4fcdf32f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..a02ca46ce6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -145,6 +145,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
@@ -934,37 +936,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1142,14 +1113,58 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6e068f2c8b..09f084a57d 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -100,6 +100,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat list of RelPermissionInfo "*/
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -725,7 +727,8 @@ typedef struct RelOptInfo
 
 	/* Information about foreign tables and foreign joins */
 	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
+	Oid			userid;			/* identifies user to check access as; set
+								 * in non-foreign table relations too! */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ec9a8b0c81..7b39ca264a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
+#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -65,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -636,6 +640,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index e9472f2f73..1ec96d89bd 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 9a15de5025..5b884ad96f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
 #endif							/* PLANNER_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1500de2dd0..dd4b751f73 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -233,7 +235,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -267,6 +270,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..ae06487670 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,8 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist, Oid relid);
+extern List *MergeRelPermissionInfos(List *into, List *from);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist, Oid relid, bool missing_ok);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 728a60c0b0..26300cc143 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-08-26 09:13  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2021-08-26 09:13 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Fri, Aug 20, 2021 at 10:46 PM Amit Langote <[email protected]> wrote:
> On Thu, Jul 29, 2021 at 5:40 PM Amit Langote <[email protected]> wrote:
> > On Fri, Jul 2, 2021 at 9:40 AM Amit Langote <[email protected]> wrote:
> > > On Fri, Jul 2, 2021 at 12:45 AM Tom Lane <[email protected]> wrote:
> > > > Perhaps, if we separated the rtable from the required-permissions data
> > > > structure, then we could avoid pulling up otherwise-useless RTEs when
> > > > flattening a view (or even better, not make the extra RTEs in the
> > > > first place??), and thus possibly avoid that exponential planning-time
> > > > growth for nested views.
> >
> > Think I've managed to get the first part done -- getting the
> > permission-checking info out of the range table -- but have not
> > seriously attempted the second -- doing away with the OLD/NEW range
> > table entries in the view/rule action queries, assuming that is what
> > you meant in the quoted.
>
> I took a stab at the 2nd part, implemented in the attached 0002.
>
> The patch removes UpdateRangeTableOfViewParse() which would add the
> dummy OLD/NEW entries to a view rule's action query's rtable
>
> I haven't yet checked how this further improves the performance for
> the case discussed at [1] that prompted this.
>
> [1] https://www.postgresql.org/message-id/flat/797aff54-b49b-4914-9ff9-aa42564a4d7d%40www.fastmail.com

I checked the time required to do explain select * from v512 (worst
case), using the setup described at the above link and I get the
following numbers:

HEAD: 119.774 ms
0001  : 129.802 ms
0002  : 109.456 ms

So it appears that applying only 0001 makes things a bit worse for
this case.  That seems to have to do with the following addition in
pull_up_simple_subquery():

@@ -1131,6 +1131,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node
*jtnode, RangeTblEntry *rte,
     */
    parse->rtable = list_concat(parse->rtable, subquery->rtable);

+   parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+                                                subquery->relpermlist);
+

What it does is pull up the RelPermissionInfo nodes in the subquery
being pulled up into the parent query and it's not a simple
list_concat(), because I decided that it's better to de-duplicate the
entries for a given relation OID even across subqueries.

Things get better than HEAD with 0002, because less work needs to be
done in the rewriter when copying the subqueries into the main query,
especially the range table, which only has 1 entry now, not 3 per
view.

Attached updated patches.  I wrote a longer commit message for 0002 this time.

-- 
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v4-0001-Rework-query-relation-permission-checking.patch (140.3K, ../../CA+HiwqGwYbrSOH1FLNVr=bp=oZB81X7d8pJzpB52Z19Zs=otow@mail.gmail.com/2-v4-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 624d523d975c2306a88dd03a73540b9ba313805d Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v4 1/2] Rework query relation permission checking

Currently, any information about the permissions to be checked is
stored in query's range table entries.  Only the permissions of
RTE_RELATION entries need be checked, that too only for the relations
that are directly mentioned in the query, not those added afterwards,
say, due to expanding inheritance.  This arrangement means that the
executor must wade through the range table to find those entries that
need their permissions checked, which can be severely wasteful when
there are many entries that belong to inheritance child tables whose
permissions need not be checked.

This commit moves the permission checking information out of the
range table entries into a new node type called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table, keyed on relation OIDs.

The list is initialized by the parser when initializing the range
table.  The rewriter can add more entries to it as rules/views are
expanded.  Finally, the planner combines the lists of the individual
subqueries into one flat list that is passed down to the executor.
---
 contrib/postgres_fdw/postgres_fdw.c       |  75 +++++---
 contrib/sepgsql/dml.c                     |  42 ++--
 contrib/sepgsql/hooks.c                   |   6 +-
 src/backend/access/common/attmap.c        |  13 +-
 src/backend/access/common/tupconvert.c    |   2 +-
 src/backend/catalog/partition.c           |   3 +-
 src/backend/commands/copy.c               |  18 +-
 src/backend/commands/copyfrom.c           |   9 +
 src/backend/commands/indexcmds.c          |   3 +-
 src/backend/commands/tablecmds.c          |  24 ++-
 src/backend/commands/view.c               |   4 -
 src/backend/executor/execMain.c           | 105 +++++-----
 src/backend/executor/execParallel.c       |   1 +
 src/backend/executor/execPartition.c      |  12 +-
 src/backend/executor/execUtils.c          | 137 +++++++++----
 src/backend/nodes/copyfuncs.c             |  31 ++-
 src/backend/nodes/equalfuncs.c            |  16 +-
 src/backend/nodes/outfuncs.c              |  28 ++-
 src/backend/nodes/readfuncs.c             |  22 ++-
 src/backend/optimizer/plan/createplan.c   |   6 +-
 src/backend/optimizer/plan/planner.c      |   6 +
 src/backend/optimizer/plan/setrefs.c      |   8 +-
 src/backend/optimizer/plan/subselect.c    |   2 +
 src/backend/optimizer/prep/prepjointree.c |  26 +++
 src/backend/optimizer/util/inherit.c      | 172 ++++++++++++-----
 src/backend/optimizer/util/relnode.c      |   9 +-
 src/backend/parser/analyze.c              |  61 ++++--
 src/backend/parser/parse_clause.c         |   9 +-
 src/backend/parser/parse_relation.c       | 223 ++++++++++++++--------
 src/backend/parser/parse_target.c         |  19 +-
 src/backend/parser/parse_utilcmd.c        |   9 +-
 src/backend/replication/logical/worker.c  |  13 +-
 src/backend/rewrite/rewriteDefine.c       |  15 +-
 src/backend/rewrite/rewriteHandler.c      | 178 ++++++++---------
 src/backend/rewrite/rowsecurity.c         |  24 ++-
 src/backend/statistics/extended_stats.c   |   7 +-
 src/backend/utils/adt/ri_triggers.c       |  34 ++--
 src/backend/utils/adt/selfuncs.c          |  19 +-
 src/include/access/attmap.h               |   6 +-
 src/include/commands/copyfrom_internal.h  |   3 +-
 src/include/executor/executor.h           |   7 +-
 src/include/nodes/execnodes.h             |   9 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/parsenodes.h            |  81 ++++----
 src/include/nodes/pathnodes.h             |   5 +-
 src/include/nodes/plannodes.h             |   5 +
 src/include/optimizer/inherit.h           |   1 +
 src/include/optimizer/planner.h           |   1 +
 src/include/parser/parse_node.h           |   6 +-
 src/include/parser/parse_relation.h       |   3 +
 src/include/rewrite/rewriteHandler.h      |   2 +-
 51 files changed, 971 insertions(+), 550 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9d443baf02..39d28df3b6 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -457,7 +459,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -622,7 +625,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -656,12 +658,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermisssions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1514,16 +1516,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermisssions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1803,7 +1804,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1882,6 +1884,29 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RelPermissionInfo *perminfo;
+	Oid		relid;
+	Oid		result;
+
+	if (relInfo->ri_RootResultRelInfo)
+		relid = RelationGetRelid(relInfo->ri_RootResultRelInfo->ri_RelationDesc);
+	else
+		relid = RelationGetRelid(relInfo->ri_RelationDesc);
+
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, relid, false);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1893,6 +1918,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1900,6 +1926,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1923,6 +1950,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1934,7 +1962,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2126,6 +2155,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2203,6 +2233,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2219,7 +2251,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2605,7 +2638,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2625,13 +2657,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3929,12 +3960,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3946,12 +3977,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 1f96e8b507..44ec89840f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 19a3ffb7ff..036a4c97f2 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 32405f8610..85221ada52 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 64f54393f3..f5624eeab9 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 790f4ccb92..ffc45efb32 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b33951e0c..5118343f02 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..4e9e94eee0 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -654,6 +654,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1384,7 +1390,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..75e4b0cbf3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1230,7 +1230,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..7f71f5cfb3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1172,7 +1172,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9494,7 +9495,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9636,7 +9638,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9822,7 +9825,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	table_close(pg_constraint, RowShareLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -9969,7 +9973,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -11878,7 +11883,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -17561,7 +17567,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18447,7 +18454,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..5bfa730e8a 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -381,10 +381,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..5bde876b78 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -73,7 +73,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -89,8 +89,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -552,8 +552,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,38 +565,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -604,32 +605,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermisssions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -663,14 +653,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -695,15 +685,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -711,12 +701,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -771,17 +761,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -813,9 +800,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1773,7 +1761,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1858,7 +1846,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1910,7 +1899,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2017,7 +2007,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f8a4a40e7b..6c932b8261 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..f4456a1aca 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -574,7 +574,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -631,7 +632,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -773,7 +775,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -1040,7 +1043,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 6ef37c0886..7e649be151 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,32 +1252,76 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
+{
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
+	{
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
+
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
 /* Return a bitmap representing columns being inserted */
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
+	 * The columns are stored in estate->relpermlist.  If this ResultRelInfo
+	 * represents a child relation (a partition routing target of an INSERT or
+	 * a child UPDATE target), it doesn't have an entry of its own, so fetch
+	 * the parent's entry and map the columns to the order they are in the
+	 * partition.
 	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->insertedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
+		else
+			return perminfo->insertedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		return perminfo->insertedCols;
 	}
 	else
 	{
@@ -1295,22 +1340,28 @@ Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->updatedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
+		else
+			return perminfo->updatedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		return perminfo->updatedCols;
 	}
 	else
 		return NULL;
@@ -1321,22 +1372,28 @@ Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(rootRelInfo->ri_RelationDesc),
+								 false);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
+		else
+			return perminfo->extraUpdatedCols;
 	}
-	else if (relinfo->ri_RootResultRelInfo)
+	else if (relinfo->ri_RangeTableIndex != 0)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(estate->es_relpermlist,
+								 RelationGetRelid(relinfo->ri_RelationDesc),
+								 false);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		return perminfo->extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..db56906d17 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -776,6 +777,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1264,6 +1266,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2480,12 +2501,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3165,6 +3180,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5106,6 +5122,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..7603c04784 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -985,6 +985,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -2755,17 +2756,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -3793,6 +3802,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 87561cbb6f..2d3c239e13 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -308,6 +308,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -702,6 +703,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -988,6 +990,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2263,6 +2280,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3073,6 +3091,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3305,12 +3324,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -3992,6 +4005,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0dd1ad7dfc..d8e30b8dbb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1505,13 +1506,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_INT_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
+	READ_INT_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1590,6 +1602,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2075,6 +2088,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -2847,6 +2861,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index a5f6d678cc..700643d2c4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4051,6 +4051,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5698,7 +5701,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465..55505bfee3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5925,6 +5929,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, tableOid);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6052,6 +6057,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, tableOid);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e50624c465..b519c53f70 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -260,6 +260,10 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 */
 	add_rtes_to_flat_rtable(root, false);
 
+	/* Also add the query's RelPermissionInfos to the global list. */
+	glob->finalrelpermlist = list_concat(glob->finalrelpermlist,
+										 root->parse->relpermlist);
+
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
 	 */
@@ -438,9 +442,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..5a622cabe5 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1504,6 +1504,8 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+												 subselect->relpermlist);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 224c5153b1..b1a1eec17f 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1131,6 +1131,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	parse->relpermlist = MergeRelPermissionInfos(parse->relpermlist,
+												 subquery->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1268,6 +1271,9 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 * Append child RTEs to parent rtable.
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->relpermlist =
+		MergeRelPermissionInfos(root->parse->relpermlist,
+								subquery->relpermlist);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
@@ -1311,6 +1317,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
+		RangeTblEntry *rte = rt_fetch(rtr->rtindex, root->parse->rtable);
 		int			childRTindex;
 		AppendRelInfo *appinfo;
 
@@ -1345,6 +1352,25 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
 		rtr->rtindex = childRTindex;
 		(void) pull_up_subqueries_recurse(root, (Node *) rtr,
 										  NULL, NULL, appinfo);
+
+		/*
+		 * If the subquery was not pulled up, need to merge its relpermlist
+		 * entries into the parent query by ourselves.  It's a no-op if the
+		 * subquery is not a simple one, because its relpermlist would be
+		 * empty in that case.
+		 *
+		 * HACK: this relies on rte->subquery still being non-NULL to know
+		 * that the subquery pull-up failed; it's set to NULL by
+		 * pull_up_simple_subquery() when it succeeds.
+		 */
+		if (rte->rtekind == RTE_SUBQUERY && rte->subquery)
+		{
+			Query   *subquery = rte->subquery;
+
+			root->parse->relpermlist =
+				MergeRelPermissionInfos(root->parse->relpermlist,
+										subquery->relpermlist);
+		}
 	}
 	else if (IsA(setOp, SetOperationStmt))
 	{
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index c758172efa..49d707da5e 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte->relid, false);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,84 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist,
+									rte->relid,
+									false);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 47769cea45..6c7eeeb419 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* otherrels use the root parent's value. */
+		rel->userid = parent ? parent->userid :
+			GetRelPermissionInfo(root->parse->relpermlist,
+								 rte->relid, false)->checkAsUser;
+	}
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 15669c8238..2d929f097b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -475,6 +475,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -507,7 +508,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -626,6 +627,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -851,7 +858,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -867,8 +874,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -895,6 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1053,8 +1061,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1348,6 +1354,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1582,6 +1589,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1828,6 +1836,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2286,6 +2295,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2352,6 +2362,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2370,7 +2381,7 @@ static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2382,7 +2393,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2423,8 +2434,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2711,6 +2722,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3189,9 +3201,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist,
+														rte->relid, false);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3247,9 +3267,18 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist,
+														 rte->relid, false);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index b3f151d33b..d5537c424a 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3242,16 +3242,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7465919044..9ecbbb09f7 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1010,10 +1010,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte->relid, false);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1224,10 +1227,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1263,6 +1269,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1406,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1442,7 +1450,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize acesss permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1451,12 +1459,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte->relid);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1470,7 +1480,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1507,6 +1517,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo = NULL;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1530,7 +1541,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1539,12 +1550,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte->relid);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1558,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1628,21 +1641,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1935,20 +1942,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1960,7 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2006,20 +2006,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2094,19 +2087,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2185,19 +2172,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,6 +2193,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2335,19 +2317,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2461,16 +2437,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2482,7 +2455,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3102,6 +3075,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3119,7 +3093,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3665,3 +3642,93 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation OID and adds it into
+ *		the provided list unless added already
+ *
+ * Returns the RelPermssionInfo.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, Oid relid)
+{
+	RelPermissionInfo *perminfo;
+
+	/* Don't allow duplicate entries for a given relation. */
+	perminfo = GetRelPermissionInfo(*relpermlist, relid, true);
+	if (perminfo)
+		return perminfo;
+
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Tries to find a RelPermissionInfo for given relation OID in the
+ *		provided list, erroring out or returning NULL (depending on
+ *		missing_ok) if not found
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, Oid relid, bool missing_ok)
+{
+	ListCell *lc;
+
+	Assert(OidIsValid(relid));
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo	*perminfo = lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfo in the 'from' list to the 'into' list,
+ *		"merging" the contents of any that are present in both.
+ *
+ * Returns the destination list.
+ */
+List *
+MergeRelPermissionInfos(List *into, List *from)
+{
+	ListCell *l;
+
+	foreach(l, from)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *target;
+
+		target = AddRelPermissionInfo(&into, perminfo->relid);
+
+		/* "merge" proprties. */
+		target->inh = perminfo->inh;
+		target->requiredPerms |= perminfo->requiredPerms;
+		if (!OidIsValid(target->checkAsUser))
+			target->checkAsUser = perminfo->checkAsUser;
+		target->selectedCols = bms_union(target->selectedCols,
+										 perminfo->selectedCols);
+		target->insertedCols = bms_union(target->insertedCols,
+										 perminfo->insertedCols);
+		target->updatedCols = bms_union(target->updatedCols,
+										perminfo->updatedCols);
+		target->extraUpdatedCols = bms_union(target->extraUpdatedCols,
+											 perminfo->extraUpdatedCols);
+	}
+
+	return into;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6e8fbc4780..6d308b2cd8 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1142,7 +1142,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e5eefdbd43..a0842bf3a5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3010,9 +3011,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3068,6 +3066,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3110,8 +3109,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 38b493e4f5..db1b1f1002 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -155,6 +155,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -463,6 +464,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte->relid);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1691,7 +1694,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	if (handle_streamed_transaction(LOGICAL_REP_MSG_UPDATE, s))
@@ -1731,7 +1734,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1741,14 +1744,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..5ffcb58306 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -821,18 +822,20 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 54fd6d6fb2..92462daa61 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,25 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	sub_action->relpermlist =
+		MergeRelPermissionInfos(copyObject(parsetree->relpermlist),
+								sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1566,16 +1569,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1593,9 +1598,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1680,8 +1685,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1722,18 +1726,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1822,26 +1814,6 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->tablesample = NULL;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1870,8 +1842,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist,
+											rte->relid, false);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3012,6 +2989,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3147,6 +3127,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist,
+										 base_rte->relid,
+										 false);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3219,51 +3202,53 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * Mark the new target RTE for the permissions checks that we want to
+	 * Mark the new target relation for the permissions checks that we want to
 	 * enforce against the view owner, as distinct from the query caller.  At
 	 * the relation level, require the same INSERT/UPDATE/DELETE permissions
-	 * that the query caller needs against the view.  We drop the ACL_SELECT
-	 * bit that is presumably in new_rte->requiredPerms initially.
+	 * that the query caller needs against the view.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
-	new_rte->checkAsUser = view->rd_rel->relowner;
-	new_rte->requiredPerms = view_rte->requiredPerms;
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist,
+										 view_rte->relid, false);
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist,
+										new_rte->relid);
+	new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3367,7 +3352,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3378,8 +3363,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3653,6 +3636,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3664,6 +3648,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist,
+										   rt_entry->relid, false);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3749,7 +3735,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_DELETE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e10f94904e..9da976e375 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte->relid, false);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -241,7 +247,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * ALL or SELECT USING policy.
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -284,7 +290,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -340,7 +346,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -369,7 +375,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -386,8 +392,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 2e55913bc8..f438b71229 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -30,6 +30,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1555,6 +1556,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1602,10 +1604,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..486fedfd43 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1308,8 +1308,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1327,32 +1327,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1362,9 +1356,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..15bb356572 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5135,7 +5135,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ?
+									onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5187,7 +5188,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ?
+											onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5271,7 +5273,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ?
+						onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5319,7 +5322,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ?
+								onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5380,6 +5384,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5388,7 +5393,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ?
+				onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5457,7 +5463,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ?
+					onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 778fa27fd1..f3ce859395 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 4d68d9cceb..752e204ec7 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3dc03c913e..19ed90b956 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -598,6 +599,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 37cb4f3d59..5d44c88238 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -522,6 +522,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 } ResultRelInfo;
@@ -563,6 +571,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..ee4fcdf32f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..a02ca46ce6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -145,6 +145,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
@@ -934,37 +936,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1142,14 +1113,58 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1abe233db2..244b5c5792 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat list of RelPermissionInfo "*/
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -726,7 +728,8 @@ typedef struct RelOptInfo
 
 	/* Information about foreign tables and foreign joins */
 	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
+	Oid			userid;			/* identifies user to check access as; set
+								 * in non-foreign table relations too! */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ec9a8b0c81..7b39ca264a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
+#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -65,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -636,6 +640,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index e9472f2f73..1ec96d89bd 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 9a15de5025..5b884ad96f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
 #endif							/* PLANNER_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1500de2dd0..dd4b751f73 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -233,7 +235,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -267,6 +270,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..ae06487670 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,8 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist, Oid relid);
+extern List *MergeRelPermissionInfos(List *into, List *from);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist, Oid relid, bool missing_ok);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 728a60c0b0..26300cc143 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



  [application/octet-stream] v4-0002-Do-not-add-OLD-NEW-RTEs-to-stored-view-rule-actio.patch (114.7K, ../../CA+HiwqGwYbrSOH1FLNVr=bp=oZB81X7d8pJzpB52Z19Zs=otow@mail.gmail.com/3-v4-0002-Do-not-add-OLD-NEW-RTEs-to-stored-view-rule-actio.patch)
  download | inline diff:
From cdf76be568e0eeb057e1aa24b39ee912fdcbb315 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v4 2/2] Do not add OLD/NEW RTEs to stored view rule actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes those RTEs unnecessary
for that purpose.

Also, this commit teaches the rewriter to add an RTE for any view
relations referenced in a query so that they are locked during
execution. The same RTE also ensures that the view relation OIDs are
correctly reported into PlannedStmt.relationOids.

As this changes the shape of the view queries stored in the catalog,
a bunch of regression tests that display those queries now display
them slightly differently, so their outputs have been updated.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  29 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  12 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 778 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 22 files changed, 734 insertions(+), 801 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e3ee30f1aa..5ed3c155cd 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2478,7 +2478,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2541,7 +2541,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6346,10 +6346,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6437,10 +6437,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 5bfa730e8a..e596683f27 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -323,78 +323,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -557,12 +485,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 92462daa61..1002f1dc11 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1685,7 +1685,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1803,16 +1804,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a4ee54d516..03bc29a046 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2081,7 +2081,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2097,7 +2097,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2113,7 +2113,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2129,7 +2129,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2147,7 +2147,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -2952,7 +2952,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 5949996ebc..57df8f7889 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1535,7 +1535,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1587,7 +1587,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1603,7 +1603,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1619,7 +1619,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2104,15 +2104,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..cfa69aab78 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2414,8 +2414,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2426,8 +2426,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2453,8 +2453,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2466,8 +2466,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 70133df804..4748db0305 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f06ae543e4..5f4c5c0db6 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f50ef76685..dee7928ba2 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -341,10 +341,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -386,9 +386,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -402,9 +402,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -418,9 +418,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -434,9 +434,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -451,9 +451,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -467,9 +467,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -483,9 +483,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -499,9 +499,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -516,9 +516,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -532,9 +532,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -548,9 +548,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -564,9 +564,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -581,9 +581,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -597,9 +597,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -613,9 +613,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -629,9 +629,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -647,9 +647,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -663,9 +663,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -679,9 +679,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -695,9 +695,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -714,9 +714,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -730,9 +730,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -746,9 +746,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -762,9 +762,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1251,10 +1251,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1531,9 +1531,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1550,9 +1550,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1573,9 +1573,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1665,8 +1665,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1895,7 +1895,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -1947,19 +1947,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 7b6b0bb4f9..2b85967c1f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -179,12 +179,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 4c467c1b15..71d6ec7034 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index b75afcc01a..493ad70db8 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -633,10 +633,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -648,10 +648,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -666,10 +666,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -680,10 +680,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index cafca1f9ae..6347c62774 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..fefad636ca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1291,21 +1291,21 @@ iexit| SELECT ih.name,
    FROM ihighway ih,
     ramp r
   WHERE (ih.thepath ## r.thepath);
-key_dependent_view| SELECT view_base_table.key,
-    view_base_table.data
+key_dependent_view| SELECT key,
+    data
    FROM view_base_table
-  GROUP BY view_base_table.key;
+  GROUP BY key;
 key_dependent_view_no_cols| SELECT
    FROM view_base_table
-  GROUP BY view_base_table.key
- HAVING (length((view_base_table.data)::text) > 0);
-mvtest_tv| SELECT mvtest_t.type,
-    sum(mvtest_t.amt) AS totamt
+  GROUP BY key
+ HAVING (length((data)::text) > 0);
+mvtest_tv| SELECT type,
+    sum(amt) AS totamt
    FROM mvtest_t
-  GROUP BY mvtest_t.type;
-mvtest_tvv| SELECT sum(mvtest_tv.totamt) AS grandtot
+  GROUP BY type;
+mvtest_tvv| SELECT sum(totamt) AS grandtot
    FROM mvtest_tv;
-mvtest_tvvmv| SELECT mvtest_tvvm.grandtot
+mvtest_tvvmv| SELECT grandtot
    FROM mvtest_tvvm;
 pg_available_extension_versions| SELECT e.name,
     e.version,
@@ -1324,50 +1324,50 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1380,22 +1380,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1435,13 +1435,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1459,10 +1459,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1708,23 +1708,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1738,10 +1738,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1809,13 +1809,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1828,57 +1828,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1901,8 +1901,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1911,13 +1911,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2094,41 +2094,41 @@ pg_stat_subscription| SELECT su.oid AS subid,
     st.latest_end_time
    FROM (pg_subscription su
      LEFT JOIN pg_stat_get_subscription(NULL::oid) st(subid, relid, pid, received_lsn, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time) ON ((st.subid = su.oid)));
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2138,68 +2138,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2216,19 +2216,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2238,19 +2238,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2289,64 +2289,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname, t.oid, x.indexrelid;
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2529,24 +2529,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -2572,26 +2572,26 @@ pg_views| SELECT n.nspname AS schemaname,
    FROM (pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = 'v'::"char");
-rtest_v1| SELECT rtest_t1.a,
-    rtest_t1.b
+rtest_v1| SELECT a,
+    b
    FROM rtest_t1;
 rtest_vcomp| SELECT x.part,
     (x.size * y.factor) AS size_in_cm
    FROM rtest_comp x,
     rtest_unitfact y
   WHERE (x.unit = y.unit);
-rtest_vview1| SELECT x.a,
-    x.b
+rtest_vview1| SELECT a,
+    b
    FROM rtest_view1 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
           WHERE (y.a = x.a)));
-rtest_vview2| SELECT rtest_view1.a,
-    rtest_view1.b
+rtest_vview2| SELECT a,
+    b
    FROM rtest_view1
-  WHERE rtest_view1.v;
-rtest_vview3| SELECT x.a,
-    x.b
+  WHERE v;
+rtest_vview3| SELECT a,
+    b
    FROM rtest_vview2 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
@@ -2603,9 +2603,9 @@ rtest_vview4| SELECT x.a,
     rtest_view2 y
   WHERE (x.a = y.a)
   GROUP BY x.a, x.b;
-rtest_vview5| SELECT rtest_view1.a,
-    rtest_view1.b,
-    rtest_viewfunc1(rtest_view1.a) AS refcount
+rtest_vview5| SELECT a,
+    b,
+    rtest_viewfunc1(a) AS refcount
    FROM rtest_view1;
 shoe| SELECT sh.shoename,
     sh.sh_avail,
@@ -2635,20 +2635,20 @@ shoelace| SELECT s.sl_name,
    FROM shoelace_data s,
     unit u
   WHERE (s.sl_unit = u.un_name);
-shoelace_candelete| SELECT shoelace_obsolete.sl_name,
-    shoelace_obsolete.sl_avail,
-    shoelace_obsolete.sl_color,
-    shoelace_obsolete.sl_len,
-    shoelace_obsolete.sl_unit,
-    shoelace_obsolete.sl_len_cm
+shoelace_candelete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace_obsolete
-  WHERE (shoelace_obsolete.sl_avail = 0);
-shoelace_obsolete| SELECT shoelace.sl_name,
-    shoelace.sl_avail,
-    shoelace.sl_color,
-    shoelace.sl_len,
-    shoelace.sl_unit,
-    shoelace.sl_len_cm
+  WHERE (sl_avail = 0);
+shoelace_obsolete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace
   WHERE (NOT (EXISTS ( SELECT shoe.shoename
            FROM shoe
@@ -2659,14 +2659,14 @@ street| SELECT r.name,
    FROM ONLY road r,
     real_city c
   WHERE (c.outline ## r.thepath);
-test_tablesample_v1| SELECT test_tablesample.id
+test_tablesample_v1| SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
-test_tablesample_v2| SELECT test_tablesample.id
+test_tablesample_v2| SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
-toyemp| SELECT emp.name,
-    emp.age,
-    emp.location,
-    (12 * emp.salary) AS annualsal
+toyemp| SELECT name,
+    age,
+    location,
+    (12 * salary) AS annualsal
    FROM emp;
 SELECT tablename, rulename, definition FROM pg_rules
 WHERE schemaname IN ('pg_catalog', 'public')
@@ -3256,7 +3256,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3295,8 +3295,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3308,8 +3308,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3321,8 +3321,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3334,8 +3334,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 078358d226..15a64aca0c 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5d124cf96f..ed0f85e113 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1251,8 +1251,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..a026946fb4 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1666,19 +1666,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1719,17 +1719,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1759,17 +1759,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -1800,16 +1800,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -1831,15 +1831,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index bb9ff7f07b..5e4612fff1 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 3523a7dcc1..d262a7944e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -802,9 +802,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1291,9 +1291,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1313,9 +1313,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-09-06 19:35  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2021-09-06 19:35 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

Got this warning:

/pgsql/source/master/contrib/postgres_fdw/postgres_fdw.c: In function 'GetResultRelCheckAsUser':
/pgsql/source/master/contrib/postgres_fdw/postgres_fdw.c:1898:7: warning: unused variable 'result' [-Wunused-variable]
  Oid  result;
       ^~~~~~

I think the idea that GetRelPermissionInfo always has to scan the
complete list by OID is a nonstarter.  Maybe it would be possible to
store the list index of the PermissionInfo element in the RelOptInfo or
the RTE?  Maybe use special negative values if unknown (it knows to
search the first time) or known non-existant (probably a coding error
condition, maybe not necessary to have this)

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-09-10 03:22  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2021-09-10 03:22 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

Thanks Alvaro for taking a look at this.

On Tue, Sep 7, 2021 at 4:35 AM Alvaro Herrera <[email protected]> wrote:
> Got this warning:
>
> /pgsql/source/master/contrib/postgres_fdw/postgres_fdw.c: In function 'GetResultRelCheckAsUser':
> /pgsql/source/master/contrib/postgres_fdw/postgres_fdw.c:1898:7: warning: unused variable 'result' [-Wunused-variable]
>   Oid  result;
>        ^~~~~~

Fixed.

> I think the idea that GetRelPermissionInfo always has to scan the
> complete list by OID is a nonstarter.  Maybe it would be possible to
> store the list index of the PermissionInfo element in the RelOptInfo or
> the RTE?  Maybe use special negative values if unknown (it knows to
> search the first time) or known non-existant (probably a coding error
> condition, maybe not necessary to have this)

I implemented this by adding an Index field in RangeTblEntry, because
GetRelPermissionInfo() is used in all phases of query processing and
only RTEs exist from start to end.  I did have to spend some time
getting that approach right (get `make check` to pass!), especially to
ensure that the indexes remain in sync during the merging of
RelPermissionInfo across subqueries.  The comments I wrote around
GetRelPermissionInfo(), MergeRelPermissionInfos() functions should
hopefully make things clear.  Though, I do have a slightly uneasy
feeling around the fact that RTEs now store information that is
computed using some non-trivial logic, whereas most other fields are
simple catalog state or trivial details extracted from how the query
is spelled out by the user.

I also noticed that setrefs.c: add_rtes_to_flat_rtable() was still
doing things -- adding dead subquery RTEs and any RTEs referenced in
the underlying subquery to flat rtable -- that the new approach of
permissions handling makes unnecessary.  I fixed that oversight in the
updated patch.  A benefit from that simplification is that there is
now a single loop over rtable in that function rather than two that
were needed before.

-- 
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v5-0001-Rework-query-relation-permission-checking.patch (150.6K, ../../CA+HiwqF3DT65a4o9zHBWQx0P2DHyGc3gv2HzJ0rR54Bj3gjcpA@mail.gmail.com/2-v5-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 3410f01e1e3992531a12282a0f6c016984661df2 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v5 1/2] Rework query relation permission checking

Currently, any information about the permissions to be checked is
stored in query's range table entries.  Only the permissions of
RTE_RELATION entries need be checked, that too only for the relations
that are directly mentioned in the query, not those added afterwards,
say, due to expanding inheritance.  This arrangement means that the
executor must wade through the range table to find those entries that
need their permissions checked, which can be severely wasteful when
there are many entries that belong to inheritance child tables whose
permissions need not be checked.

This commit moves the permission checking information out of the
range table entries into a new node type called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  Also, RTEs get a new Index field 'perminfoindex' to store the
index in the aforementioned list of the RelPermissionInfo belonging
to the RTE.

The list is initialized by the parser when initializing the range
table.  The rewriter can add more entries to it as rules/views are
expanded.  Finally, the planner combines the lists of the individual
subqueries into one flat list that is passed down to the executor.
---
 contrib/postgres_fdw/postgres_fdw.c       |  81 ++++--
 contrib/sepgsql/dml.c                     |  42 ++-
 contrib/sepgsql/hooks.c                   |   6 +-
 src/backend/access/common/attmap.c        |  13 +-
 src/backend/access/common/tupconvert.c    |   2 +-
 src/backend/catalog/partition.c           |   3 +-
 src/backend/commands/copy.c               |  18 +-
 src/backend/commands/copyfrom.c           |   9 +
 src/backend/commands/indexcmds.c          |   3 +-
 src/backend/commands/tablecmds.c          |  24 +-
 src/backend/commands/view.c               |   4 -
 src/backend/executor/execMain.c           | 105 ++++----
 src/backend/executor/execParallel.c       |   1 +
 src/backend/executor/execPartition.c      |  12 +-
 src/backend/executor/execUtils.c          | 159 ++++++++----
 src/backend/nodes/copyfuncs.c             |  32 ++-
 src/backend/nodes/equalfuncs.c            |  17 +-
 src/backend/nodes/outfuncs.c              |  29 ++-
 src/backend/nodes/readfuncs.c             |  23 +-
 src/backend/optimizer/plan/createplan.c   |   6 +-
 src/backend/optimizer/plan/planner.c      |   6 +
 src/backend/optimizer/plan/setrefs.c      | 123 +++------
 src/backend/optimizer/plan/subselect.c    |   5 +
 src/backend/optimizer/prep/prepjointree.c |  10 +
 src/backend/optimizer/util/inherit.c      | 170 +++++++++----
 src/backend/optimizer/util/relnode.c      |   9 +-
 src/backend/parser/analyze.c              |  62 +++--
 src/backend/parser/parse_clause.c         |   9 +-
 src/backend/parser/parse_relation.c       | 296 ++++++++++++++++------
 src/backend/parser/parse_target.c         |  19 +-
 src/backend/parser/parse_utilcmd.c        |   9 +-
 src/backend/replication/logical/worker.c  |  13 +-
 src/backend/rewrite/rewriteDefine.c       |  15 +-
 src/backend/rewrite/rewriteHandler.c      | 175 ++++++-------
 src/backend/rewrite/rowsecurity.c         |  24 +-
 src/backend/statistics/extended_stats.c   |   7 +-
 src/backend/utils/adt/ri_triggers.c       |  34 +--
 src/backend/utils/adt/selfuncs.c          |  19 +-
 src/include/access/attmap.h               |   6 +-
 src/include/commands/copyfrom_internal.h  |   3 +-
 src/include/executor/executor.h           |   7 +-
 src/include/nodes/execnodes.h             |   9 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/parsenodes.h            |  87 ++++---
 src/include/nodes/pathnodes.h             |   5 +-
 src/include/nodes/plannodes.h             |   5 +
 src/include/optimizer/inherit.h           |   1 +
 src/include/optimizer/planner.h           |   1 +
 src/include/parser/parse_node.h           |   6 +-
 src/include/parser/parse_relation.h       |   3 +
 src/include/rewrite/rewriteHandler.h      |   2 +-
 51 files changed, 1082 insertions(+), 648 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 76d4fea21c..5c007d71ef 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -457,7 +459,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -622,7 +625,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -656,12 +658,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermisssions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1514,16 +1516,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermisssions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1803,7 +1804,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1882,6 +1884,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte, false);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1893,6 +1924,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1900,6 +1932,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1923,6 +1956,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1934,7 +1968,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2126,6 +2161,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2203,6 +2239,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2219,7 +2257,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2605,7 +2644,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2625,13 +2663,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3929,12 +3966,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3946,12 +3983,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 1f96e8b507..44ec89840f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 19a3ffb7ff..036a4c97f2 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 32405f8610..85221ada52 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 64f54393f3..f5624eeab9 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 790f4ccb92..ffc45efb32 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f4853141..522b800efe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..4e9e94eee0 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -654,6 +654,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1384,7 +1390,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..75e4b0cbf3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1230,7 +1230,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..7f71f5cfb3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1172,7 +1172,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9494,7 +9495,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9636,7 +9638,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9822,7 +9825,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	table_close(pg_constraint, RowShareLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -9969,7 +9973,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -11878,7 +11883,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -17561,7 +17567,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18447,7 +18454,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..5bfa730e8a 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -381,10 +381,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..5bde876b78 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -73,7 +73,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -89,8 +89,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -552,8 +552,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,38 +565,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -604,32 +605,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermisssions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -663,14 +653,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -695,15 +685,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -711,12 +701,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -771,17 +761,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -813,9 +800,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1773,7 +1761,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1858,7 +1846,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1910,7 +1899,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2017,7 +2007,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f8a4a40e7b..6c932b8261 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..f4456a1aca 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -574,7 +574,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -631,7 +632,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -773,7 +775,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -1040,7 +1043,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index ad11392b99..328aa6de53 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte, false);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 83ec2a369e..6123a3480e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -776,6 +777,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1264,6 +1266,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2455,6 +2476,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(perminfoindex);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
@@ -2480,12 +2502,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3170,6 +3186,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5131,6 +5148,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4bad709f83..af2204b58a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -985,6 +985,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -2736,6 +2737,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(perminfoindex);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
@@ -2761,17 +2763,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -3818,6 +3828,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 36e618611f..75e24a96fb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -308,6 +308,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -702,6 +703,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -988,6 +990,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2263,6 +2280,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3073,6 +3091,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3252,6 +3271,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3305,12 +3325,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -3993,6 +4007,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0dd1ad7dfc..8f7dab5c24 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1442,6 +1443,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -1505,13 +1507,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_INT_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
+	READ_INT_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1590,6 +1603,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2075,6 +2089,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -2847,6 +2862,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index a5f6d678cc..700643d2c4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4051,6 +4051,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5698,7 +5701,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465..be076d2d3c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5925,6 +5929,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6052,6 +6057,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e50624c465..0e0bfbd07e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,9 +104,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -258,7 +256,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -308,10 +306,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down any subquery
+ * RTEs of the current plan level that did not make into the plan tree by way
+ * of being pulled up, nor turned into SubqueryScan nodes referenced in the
+ * plan tree.  Such subqueries would not thus have had any RelPermissionInfos
+ * referenced in them merged into root->parse->relpermlist, so we must force-
+ * add them to glob->finalrelpermlist.  Failing to do so would prevent the
+ * executor from performing expected permission checks for tables mentioned in
+ * such subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -324,29 +329,13 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
-	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -363,73 +352,27 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					glob->finalrelpermlist =
+						list_concat(glob->finalrelpermlist,
+									rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
-
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
-
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally, add the query's RelPermissionInfos to the global list. */
+	glob->finalrelpermlist = list_concat(glob->finalrelpermlist,
+										 root->parse->relpermlist);
 }
 
 /*
@@ -438,9 +381,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
@@ -451,6 +392,14 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
 	memcpy(newrte, rte, sizeof(RangeTblEntry));
 
+	/*
+	 * Executor may need to look up RelPermissionInfos, so must keep
+	 * perminfoindex around.  Though, the list containing the RelPermissionInfo
+	 * to which the index points will be folded into glob->finalrelpermlist, so
+	 * offset the index likewise.
+	 */
+	newrte->perminfoindex += list_length(glob->finalrelpermlist);
+
 	/* zap unneeded sub-structure */
 	newrte->tablesample = NULL;
 	newrte->subquery = NULL;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..13bfc8f84d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1505,6 +1505,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/*
+	 * Add subquery's RelPermissionInfos into the upper query.
+	 */
+	MergeRelPermissionInfos(parse, subselect->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 224c5153b1..d75e4ccc5f 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1131,6 +1131,11 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/*
+	 * Add subquery's RelPermissionInfos into the upper query.
+	 */
+	MergeRelPermissionInfos(parse, subquery->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1269,6 +1274,11 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/*
+	 * Add the child query's RelPermissionInfos into the parent query.
+	 */
+	MergeRelPermissionInfos(root->parse, subquery->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index c758172efa..1e3177fd8a 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 47769cea45..bbfd5c0fca 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* otherrels use the root parent's value. */
+		rel->userid = parent ? parent->userid :
+			GetRelPermissionInfo(root->parse->relpermlist,
+								 rte, false)->checkAsUser;
+	}
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1e..c3298e8625 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -475,6 +475,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -507,7 +508,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -626,6 +627,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -851,7 +859,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -867,8 +875,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -895,6 +903,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1053,8 +1062,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1348,6 +1355,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1582,6 +1590,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1828,6 +1837,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2299,6 +2309,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2365,6 +2376,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2383,7 +2395,7 @@ static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2395,7 +2407,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2436,8 +2448,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2724,6 +2736,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3202,9 +3215,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte,
+														false);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3260,9 +3281,18 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist,
+														 rte, false);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 078029ba1f..ad9717fffe 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3241,16 +3241,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index c5c3f26ecf..911de4ea7e 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1010,10 +1010,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte, false);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1224,10 +1227,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1263,6 +1269,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1406,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1442,7 +1450,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize acesss permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1451,12 +1459,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1470,7 +1480,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1507,6 +1517,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1530,7 +1541,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1539,12 +1550,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1558,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1628,21 +1641,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1935,20 +1942,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1960,7 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2006,20 +2006,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2094,19 +2087,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2185,19 +2172,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,6 +2193,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2335,19 +2317,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2461,16 +2437,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2482,7 +2455,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3102,6 +3075,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3119,7 +3093,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3665,3 +3642,166 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless there already
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfo(*relpermlist, rte, true);
+	if (perminfo)
+	{
+		Assert(rte->perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = rte->relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Remember the index in the RTE. */
+	Assert(rte->perminfoindex == 0);
+	rte->perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Tries to find a RelPermissionInfo for given relation in the provided
+ *		list, erroring out or returning NULL (depending on missing_ok) if not
+ *		found
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte, bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/*
+	 * No need to scan the list on OID if the RTE contains a valid index,
+	 * which is true in most cases except when MergeRelPermissionInfos() calls
+	 * us (via AddRelPermissionInfo() that is).  MergeRelPermissionInfos()
+	 * intentionally resets the original index because it could potentially
+	 * point into a different list than what we are passed, which is possible,
+	 * for example, for RTEs that were just pulled up from another subquery.
+	 * In that case, we force scanning the list by OID and reassign the index
+	 * if an entry is found.
+	 */
+	if (rte->perminfoindex > 0)
+	{
+		if (rte->perminfoindex > list_length(relpermlist))
+			elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+				 rte->relid);
+
+		perminfo = (RelPermissionInfo *) list_nth(relpermlist,
+												  rte->perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		if (rte->relid != perminfo->relid)
+			elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+				 rte->perminfoindex, perminfo->relid, rte->relid);
+
+		return perminfo;
+	}
+	else
+	{
+		ListCell *lc;
+		int		i = 0;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == rte->relid)
+			{
+				/* And set the index in RTE. */
+				rte->perminfoindex = i + 1;
+				return perminfo;
+			}
+			i++;
+		}
+
+		if (!missing_ok)
+			elog(ERROR, "permission info of relation %u not found", rte->relid);
+	}
+
+	return NULL;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source subquery given in
+ *		src_relpermlist into dest_query, "merging" the contents of any that
+ *		are present in both.
+ *
+ * This assumes that the caller has already pulled up the source subquery's
+ * RTEs into dest_query's rtable, because their perminfoindex would need to
+ * be updated to reflect their now belonging in the new mereged list.
+ */
+void
+MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		ListCell *l1;
+
+		foreach(l1, dest_query->rtable)
+		{
+			RangeTblEntry *dest_rte = (RangeTblEntry *) lfirst(l1);
+			RelPermissionInfo *dest_perminfo;
+
+			/*
+			 * Only RELATIONs have a RelPermissionInfo.  Also ignore any that
+			 * don't match the RelPermissionInfo we're trying to merge.
+			 */
+			if (dest_rte->rtekind != RTE_RELATION ||
+				dest_rte->relid != src_perminfo->relid)
+				continue;
+
+			/*
+			 * Reset the index to signal to GetRelPermissionInfo() to re-
+			 * assign the index by looking up an existing entry for the OID in
+			 * dest_query->relpermlist.
+			 */
+			dest_rte->perminfoindex = 0;
+			dest_perminfo = AddRelPermissionInfo(&dest_query->relpermlist,
+												 dest_rte);
+			/* "merge" proprties. */
+			dest_perminfo->inh = src_perminfo->inh;
+			dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+			if (!OidIsValid(dest_perminfo->checkAsUser))
+				dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+			dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+													src_perminfo->selectedCols);
+			dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+													src_perminfo->insertedCols);
+			dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+												   src_perminfo->updatedCols);
+			dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+														src_perminfo->extraUpdatedCols);
+		}
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6e8fbc4780..6d308b2cd8 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1142,7 +1142,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1d3ee53244..58b7fa8d2e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3010,9 +3011,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3068,6 +3066,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3110,8 +3109,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 8d96c926b4..3f047f0289 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -155,6 +155,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -463,6 +464,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1665,7 +1668,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	if (handle_streamed_transaction(LOGICAL_REP_MSG_UPDATE, s))
@@ -1708,7 +1711,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1718,14 +1721,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..5ffcb58306 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -821,18 +822,20 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 9521e81100..9c8062cd42 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,23 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(sub_action, parsetree->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1590,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1619,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1703,8 +1706,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1745,18 +1747,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1843,28 +1833,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1893,8 +1864,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte, false);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3035,6 +3010,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3170,6 +3148,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte,
+										 false);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3242,51 +3222,53 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * Mark the new target RTE for the permissions checks that we want to
+	 * Mark the new target relation for the permissions checks that we want to
 	 * enforce against the view owner, as distinct from the query caller.  At
 	 * the relation level, require the same INSERT/UPDATE/DELETE permissions
-	 * that the query caller needs against the view.  We drop the ACL_SELECT
-	 * bit that is presumably in new_rte->requiredPerms initially.
+	 * that the query caller needs against the view.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
-	new_rte->checkAsUser = view->rd_rel->relowner;
-	new_rte->requiredPerms = view_rte->requiredPerms;
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte,
+										 false);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
+	new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3390,7 +3372,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3401,8 +3383,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3676,6 +3656,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3687,6 +3668,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry,
+										   false);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3772,7 +3755,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_DELETE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e10f94904e..f9d0691925 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte, false);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -241,7 +247,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * ALL or SELECT USING policy.
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -284,7 +290,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -340,7 +346,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -369,7 +375,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -386,8 +392,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 2e55913bc8..f438b71229 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -30,6 +30,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1555,6 +1556,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1602,10 +1604,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..486fedfd43 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1308,8 +1308,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1327,32 +1327,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1362,9 +1356,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..15bb356572 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5135,7 +5135,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ?
+									onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5187,7 +5188,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ?
+											onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5271,7 +5273,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ?
+						onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5319,7 +5322,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ?
+								onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5380,6 +5384,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5388,7 +5393,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ?
+				onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5457,7 +5463,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ?
+					onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 778fa27fd1..f3ce859395 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 4d68d9cceb..752e204ec7 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3dc03c913e..19ed90b956 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -598,6 +599,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 37cb4f3d59..5d44c88238 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -522,6 +522,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 } ResultRelInfo;
@@ -563,6 +571,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a692eb7b09..70dc0f0888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 45e4f2a16e..c85d793bdf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -145,6 +145,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
@@ -946,37 +948,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1030,11 +1001,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1154,14 +1131,58 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1abe233db2..244b5c5792 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat list of RelPermissionInfo "*/
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -726,7 +728,8 @@ typedef struct RelOptInfo
 
 	/* Information about foreign tables and foreign joins */
 	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
+	Oid			userid;			/* identifies user to check access as; set
+								 * in non-foreign table relations too! */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ec9a8b0c81..7b39ca264a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
+#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -65,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -636,6 +640,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index e9472f2f73..1ec96d89bd 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 9a15de5025..5b884ad96f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
 #endif							/* PLANNER_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index ee179082ce..e840ebf2bb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -233,7 +235,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -267,6 +270,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..15b4c9b03c 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,8 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte, bool missing_ok);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 728a60c0b0..26300cc143 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



  [application/octet-stream] v5-0002-Do-not-add-OLD-NEW-RTEs-to-stored-view-rule-actio.patch (114.8K, ../../CA+HiwqF3DT65a4o9zHBWQx0P2DHyGc3gv2HzJ0rR54Bj3gjcpA@mail.gmail.com/3-v5-0002-Do-not-add-OLD-NEW-RTEs-to-stored-view-rule-actio.patch)
  download | inline diff:
From 63e35b63799e266a5883f8f0e6da961ebafd1328 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v5 2/2] Do not add OLD/NEW RTEs to stored view rule actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes those RTEs unnecessary
for that purpose.

Also, this commit teaches the rewriter to add an RTE for any view
relations referenced in a query so that they are locked during
execution. The same RTE also ensures that the view relation OIDs are
correctly reported into PlannedStmt.relationOids.

As this changes the shape of the view queries stored in the catalog,
a bunch of regression tests that display those queries now display
them slightly differently, so their outputs have been updated.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  31 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  12 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 778 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 22 files changed, 735 insertions(+), 802 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e3ee30f1aa..5ed3c155cd 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2478,7 +2478,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2541,7 +2541,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6346,10 +6346,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6437,10 +6437,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 5bfa730e8a..e596683f27 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -323,78 +323,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -557,12 +485,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 9c8062cd42..8482fb54c3 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1706,7 +1706,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1824,17 +1825,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e1b7e31458..aefcbab98a 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2080,7 +2080,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2096,7 +2096,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2112,7 +2112,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2128,7 +2128,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2146,7 +2146,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -2951,7 +2951,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 5949996ebc..57df8f7889 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1535,7 +1535,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1587,7 +1587,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1603,7 +1603,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1619,7 +1619,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2104,15 +2104,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4bee0c1173..cfa69aab78 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2414,8 +2414,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2426,8 +2426,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2453,8 +2453,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2466,8 +2466,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 70133df804..4748db0305 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f06ae543e4..5f4c5c0db6 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f50ef76685..dee7928ba2 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -341,10 +341,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -386,9 +386,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -402,9 +402,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -418,9 +418,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -434,9 +434,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -451,9 +451,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -467,9 +467,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -483,9 +483,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -499,9 +499,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -516,9 +516,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -532,9 +532,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -548,9 +548,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -564,9 +564,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -581,9 +581,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -597,9 +597,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -613,9 +613,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -629,9 +629,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -647,9 +647,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -663,9 +663,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -679,9 +679,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -695,9 +695,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -714,9 +714,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -730,9 +730,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -746,9 +746,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -762,9 +762,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1251,10 +1251,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1531,9 +1531,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1550,9 +1550,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1573,9 +1573,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1665,8 +1665,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1895,7 +1895,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -1947,19 +1947,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 7b6b0bb4f9..2b85967c1f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -179,12 +179,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 4c467c1b15..71d6ec7034 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index b75afcc01a..493ad70db8 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -633,10 +633,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -648,10 +648,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -666,10 +666,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -680,10 +680,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index cafca1f9ae..6347c62774 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..fefad636ca 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1291,21 +1291,21 @@ iexit| SELECT ih.name,
    FROM ihighway ih,
     ramp r
   WHERE (ih.thepath ## r.thepath);
-key_dependent_view| SELECT view_base_table.key,
-    view_base_table.data
+key_dependent_view| SELECT key,
+    data
    FROM view_base_table
-  GROUP BY view_base_table.key;
+  GROUP BY key;
 key_dependent_view_no_cols| SELECT
    FROM view_base_table
-  GROUP BY view_base_table.key
- HAVING (length((view_base_table.data)::text) > 0);
-mvtest_tv| SELECT mvtest_t.type,
-    sum(mvtest_t.amt) AS totamt
+  GROUP BY key
+ HAVING (length((data)::text) > 0);
+mvtest_tv| SELECT type,
+    sum(amt) AS totamt
    FROM mvtest_t
-  GROUP BY mvtest_t.type;
-mvtest_tvv| SELECT sum(mvtest_tv.totamt) AS grandtot
+  GROUP BY type;
+mvtest_tvv| SELECT sum(totamt) AS grandtot
    FROM mvtest_tv;
-mvtest_tvvmv| SELECT mvtest_tvvm.grandtot
+mvtest_tvvmv| SELECT grandtot
    FROM mvtest_tvvm;
 pg_available_extension_versions| SELECT e.name,
     e.version,
@@ -1324,50 +1324,50 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1380,22 +1380,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1435,13 +1435,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1459,10 +1459,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1708,23 +1708,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1738,10 +1738,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1809,13 +1809,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1828,57 +1828,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1901,8 +1901,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1911,13 +1911,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2094,41 +2094,41 @@ pg_stat_subscription| SELECT su.oid AS subid,
     st.latest_end_time
    FROM (pg_subscription su
      LEFT JOIN pg_stat_get_subscription(NULL::oid) st(subid, relid, pid, received_lsn, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time) ON ((st.subid = su.oid)));
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2138,68 +2138,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2216,19 +2216,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2238,19 +2238,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2289,64 +2289,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname, t.oid, x.indexrelid;
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2529,24 +2529,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -2572,26 +2572,26 @@ pg_views| SELECT n.nspname AS schemaname,
    FROM (pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = 'v'::"char");
-rtest_v1| SELECT rtest_t1.a,
-    rtest_t1.b
+rtest_v1| SELECT a,
+    b
    FROM rtest_t1;
 rtest_vcomp| SELECT x.part,
     (x.size * y.factor) AS size_in_cm
    FROM rtest_comp x,
     rtest_unitfact y
   WHERE (x.unit = y.unit);
-rtest_vview1| SELECT x.a,
-    x.b
+rtest_vview1| SELECT a,
+    b
    FROM rtest_view1 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
           WHERE (y.a = x.a)));
-rtest_vview2| SELECT rtest_view1.a,
-    rtest_view1.b
+rtest_vview2| SELECT a,
+    b
    FROM rtest_view1
-  WHERE rtest_view1.v;
-rtest_vview3| SELECT x.a,
-    x.b
+  WHERE v;
+rtest_vview3| SELECT a,
+    b
    FROM rtest_vview2 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
@@ -2603,9 +2603,9 @@ rtest_vview4| SELECT x.a,
     rtest_view2 y
   WHERE (x.a = y.a)
   GROUP BY x.a, x.b;
-rtest_vview5| SELECT rtest_view1.a,
-    rtest_view1.b,
-    rtest_viewfunc1(rtest_view1.a) AS refcount
+rtest_vview5| SELECT a,
+    b,
+    rtest_viewfunc1(a) AS refcount
    FROM rtest_view1;
 shoe| SELECT sh.shoename,
     sh.sh_avail,
@@ -2635,20 +2635,20 @@ shoelace| SELECT s.sl_name,
    FROM shoelace_data s,
     unit u
   WHERE (s.sl_unit = u.un_name);
-shoelace_candelete| SELECT shoelace_obsolete.sl_name,
-    shoelace_obsolete.sl_avail,
-    shoelace_obsolete.sl_color,
-    shoelace_obsolete.sl_len,
-    shoelace_obsolete.sl_unit,
-    shoelace_obsolete.sl_len_cm
+shoelace_candelete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace_obsolete
-  WHERE (shoelace_obsolete.sl_avail = 0);
-shoelace_obsolete| SELECT shoelace.sl_name,
-    shoelace.sl_avail,
-    shoelace.sl_color,
-    shoelace.sl_len,
-    shoelace.sl_unit,
-    shoelace.sl_len_cm
+  WHERE (sl_avail = 0);
+shoelace_obsolete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace
   WHERE (NOT (EXISTS ( SELECT shoe.shoename
            FROM shoe
@@ -2659,14 +2659,14 @@ street| SELECT r.name,
    FROM ONLY road r,
     real_city c
   WHERE (c.outline ## r.thepath);
-test_tablesample_v1| SELECT test_tablesample.id
+test_tablesample_v1| SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
-test_tablesample_v2| SELECT test_tablesample.id
+test_tablesample_v2| SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
-toyemp| SELECT emp.name,
-    emp.age,
-    emp.location,
-    (12 * emp.salary) AS annualsal
+toyemp| SELECT name,
+    age,
+    location,
+    (12 * salary) AS annualsal
    FROM emp;
 SELECT tablename, rulename, definition FROM pg_rules
 WHERE schemaname IN ('pg_catalog', 'public')
@@ -3256,7 +3256,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3295,8 +3295,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3308,8 +3308,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3321,8 +3321,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3334,8 +3334,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 078358d226..15a64aca0c 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5d124cf96f..ed0f85e113 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1251,8 +1251,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..a026946fb4 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1666,19 +1666,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1719,17 +1719,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1759,17 +1759,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -1800,16 +1800,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -1831,15 +1831,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index bb9ff7f07b..5e4612fff1 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 622d5da7d2..b44e6a4d5b 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -802,9 +802,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1291,9 +1291,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1313,9 +1313,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2021-12-20 07:13  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2021-12-20 07:13 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Sep 10, 2021 at 12:22 PM Amit Langote <[email protected]> wrote:
> On Tue, Sep 7, 2021 at 4:35 AM Alvaro Herrera <[email protected]> wrote:
> > I think the idea that GetRelPermissionInfo always has to scan the
> > complete list by OID is a nonstarter.  Maybe it would be possible to
> > store the list index of the PermissionInfo element in the RelOptInfo or
> > the RTE?  Maybe use special negative values if unknown (it knows to
> > search the first time) or known non-existant (probably a coding error
> > condition, maybe not necessary to have this)
>
> I implemented this by adding an Index field in RangeTblEntry, because
> GetRelPermissionInfo() is used in all phases of query processing and
> only RTEs exist from start to end.  I did have to spend some time
> getting that approach right (get `make check` to pass!), especially to
> ensure that the indexes remain in sync during the merging of
> RelPermissionInfo across subqueries.  The comments I wrote around
> GetRelPermissionInfo(), MergeRelPermissionInfos() functions should
> hopefully make things clear.  Though, I do have a slightly uneasy
> feeling around the fact that RTEs now store information that is
> computed using some non-trivial logic, whereas most other fields are
> simple catalog state or trivial details extracted from how the query
> is spelled out by the user.
>
> I also noticed that setrefs.c: add_rtes_to_flat_rtable() was still
> doing things -- adding dead subquery RTEs and any RTEs referenced in
> the underlying subquery to flat rtable -- that the new approach of
> permissions handling makes unnecessary.  I fixed that oversight in the
> updated patch.  A benefit from that simplification is that there is
> now a single loop over rtable in that function rather than two that
> were needed before.

Patch 0002 needed a rebase, because a conflicting change to
expected/rules.out has since been committed.

-- 
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v6-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-rul.patch (115.6K, ../../CA+HiwqFVvMm7UrRt7yL8MngBS0hDcCmd5DKp+jPv=HDaJsd5=w@mail.gmail.com/2-v6-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-rul.patch)
  download | inline diff:
From ffdb27d8c083bb5f948d0ab9442947edf8e2f8c5 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v6 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be
present for locking views during execition, so this commit teaches
the rewriter to add an RTE for any views mentioned in the query. The
same RTE also ensures that the view relation OIDs are correctly
reported into PlannedStmt.relationOids.

As this changes the shape of the view queries stored in the catalog
(hidden OLD/NEW RTEs no longer present), a bunch of regression tests
that display those queries now display them such that columns are
longer qualified with their relation's name in some cases, like when
only one relation is mentioned in the view's query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  31 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 778 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 22 files changed, 741 insertions(+), 808 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 5196e4797a..98c0339d93 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2485,7 +2485,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2548,7 +2548,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6403,10 +6403,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6494,10 +6494,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 5bfa730e8a..e596683f27 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -323,78 +323,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -557,12 +485,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 9c8062cd42..8482fb54c3 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1706,7 +1706,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1824,17 +1825,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fd01651d9c..264ed6d361 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2145,7 +2145,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2161,7 +2161,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2177,7 +2177,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2193,7 +2193,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2211,7 +2211,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3068,7 +3068,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index be5fa5727d..6cb6fa108c 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1535,7 +1535,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1587,7 +1587,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1603,7 +1603,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1619,7 +1619,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2104,15 +2104,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 24d1c7cd28..3992a4d6da 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2462,8 +2462,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2474,8 +2474,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2501,8 +2501,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2514,8 +2514,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 70133df804..4748db0305 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f06ae543e4..5f4c5c0db6 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f50ef76685..dee7928ba2 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -341,10 +341,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -386,9 +386,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -402,9 +402,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -418,9 +418,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -434,9 +434,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -451,9 +451,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -467,9 +467,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -483,9 +483,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -499,9 +499,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -516,9 +516,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -532,9 +532,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -548,9 +548,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -564,9 +564,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -581,9 +581,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -597,9 +597,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -613,9 +613,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -629,9 +629,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -647,9 +647,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -663,9 +663,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -679,9 +679,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -695,9 +695,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -714,9 +714,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -730,9 +730,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -746,9 +746,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -762,9 +762,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1251,10 +1251,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1531,9 +1531,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1550,9 +1550,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1573,9 +1573,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1665,8 +1665,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1895,7 +1895,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -1947,19 +1947,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 6406fb3a76..46264d4185 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -179,12 +179,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -213,12 +213,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 4c467c1b15..71d6ec7034 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 2334a1321e..2c4da34687 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b58b062b10..00a03ff04e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1291,21 +1291,21 @@ iexit| SELECT ih.name,
    FROM ihighway ih,
     ramp r
   WHERE (ih.thepath ## r.thepath);
-key_dependent_view| SELECT view_base_table.key,
-    view_base_table.data
+key_dependent_view| SELECT key,
+    data
    FROM view_base_table
-  GROUP BY view_base_table.key;
+  GROUP BY key;
 key_dependent_view_no_cols| SELECT
    FROM view_base_table
-  GROUP BY view_base_table.key
- HAVING (length((view_base_table.data)::text) > 0);
-mvtest_tv| SELECT mvtest_t.type,
-    sum(mvtest_t.amt) AS totamt
+  GROUP BY key
+ HAVING (length((data)::text) > 0);
+mvtest_tv| SELECT type,
+    sum(amt) AS totamt
    FROM mvtest_t
-  GROUP BY mvtest_t.type;
-mvtest_tvv| SELECT sum(mvtest_tv.totamt) AS grandtot
+  GROUP BY type;
+mvtest_tvv| SELECT sum(totamt) AS grandtot
    FROM mvtest_tv;
-mvtest_tvvmv| SELECT mvtest_tvvm.grandtot
+mvtest_tvvmv| SELECT grandtot
    FROM mvtest_tvvm;
 pg_available_extension_versions| SELECT e.name,
     e.version,
@@ -1324,50 +1324,50 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1380,22 +1380,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1435,13 +1435,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1459,10 +1459,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1708,23 +1708,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1738,10 +1738,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1809,13 +1809,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1828,57 +1828,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1901,8 +1901,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1911,13 +1911,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2112,41 +2112,41 @@ pg_stat_subscription_workers| SELECT w.subid,
            FROM pg_subscription_rel) sr,
     (LATERAL pg_stat_get_subscription_worker(sr.subid, sr.relid) w(subid, subrelid, last_error_relid, last_error_command, last_error_xid, last_error_count, last_error_message, last_error_time)
      JOIN pg_subscription s ON ((w.subid = s.oid)));
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2156,68 +2156,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2234,19 +2234,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2256,19 +2256,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2307,64 +2307,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname, t.oid, x.indexrelid;
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2547,24 +2547,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -2590,26 +2590,26 @@ pg_views| SELECT n.nspname AS schemaname,
    FROM (pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = 'v'::"char");
-rtest_v1| SELECT rtest_t1.a,
-    rtest_t1.b
+rtest_v1| SELECT a,
+    b
    FROM rtest_t1;
 rtest_vcomp| SELECT x.part,
     (x.size * y.factor) AS size_in_cm
    FROM rtest_comp x,
     rtest_unitfact y
   WHERE (x.unit = y.unit);
-rtest_vview1| SELECT x.a,
-    x.b
+rtest_vview1| SELECT a,
+    b
    FROM rtest_view1 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
           WHERE (y.a = x.a)));
-rtest_vview2| SELECT rtest_view1.a,
-    rtest_view1.b
+rtest_vview2| SELECT a,
+    b
    FROM rtest_view1
-  WHERE rtest_view1.v;
-rtest_vview3| SELECT x.a,
-    x.b
+  WHERE v;
+rtest_vview3| SELECT a,
+    b
    FROM rtest_vview2 x
   WHERE (0 < ( SELECT count(*) AS count
            FROM rtest_view2 y
@@ -2621,9 +2621,9 @@ rtest_vview4| SELECT x.a,
     rtest_view2 y
   WHERE (x.a = y.a)
   GROUP BY x.a, x.b;
-rtest_vview5| SELECT rtest_view1.a,
-    rtest_view1.b,
-    rtest_viewfunc1(rtest_view1.a) AS refcount
+rtest_vview5| SELECT a,
+    b,
+    rtest_viewfunc1(a) AS refcount
    FROM rtest_view1;
 shoe| SELECT sh.shoename,
     sh.sh_avail,
@@ -2653,20 +2653,20 @@ shoelace| SELECT s.sl_name,
    FROM shoelace_data s,
     unit u
   WHERE (s.sl_unit = u.un_name);
-shoelace_candelete| SELECT shoelace_obsolete.sl_name,
-    shoelace_obsolete.sl_avail,
-    shoelace_obsolete.sl_color,
-    shoelace_obsolete.sl_len,
-    shoelace_obsolete.sl_unit,
-    shoelace_obsolete.sl_len_cm
+shoelace_candelete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace_obsolete
-  WHERE (shoelace_obsolete.sl_avail = 0);
-shoelace_obsolete| SELECT shoelace.sl_name,
-    shoelace.sl_avail,
-    shoelace.sl_color,
-    shoelace.sl_len,
-    shoelace.sl_unit,
-    shoelace.sl_len_cm
+  WHERE (sl_avail = 0);
+shoelace_obsolete| SELECT sl_name,
+    sl_avail,
+    sl_color,
+    sl_len,
+    sl_unit,
+    sl_len_cm
    FROM shoelace
   WHERE (NOT (EXISTS ( SELECT shoe.shoename
            FROM shoe
@@ -2677,14 +2677,14 @@ street| SELECT r.name,
    FROM ONLY road r,
     real_city c
   WHERE (c.outline ## r.thepath);
-test_tablesample_v1| SELECT test_tablesample.id
+test_tablesample_v1| SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
-test_tablesample_v2| SELECT test_tablesample.id
+test_tablesample_v2| SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
-toyemp| SELECT emp.name,
-    emp.age,
-    emp.location,
-    (12 * emp.salary) AS annualsal
+toyemp| SELECT name,
+    age,
+    location,
+    (12 * salary) AS annualsal
    FROM emp;
 SELECT tablename, rulename, definition FROM pg_rules
 WHERE schemaname IN ('pg_catalog', 'public')
@@ -3274,7 +3274,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3313,8 +3313,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3326,8 +3326,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3339,8 +3339,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3352,8 +3352,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5d124cf96f..ed0f85e113 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1251,8 +1251,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..a026946fb4 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1666,19 +1666,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1719,17 +1719,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1759,17 +1759,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -1800,16 +1800,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -1831,15 +1831,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index bb9ff7f07b..5e4612fff1 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 75e61460d9..cb76789cb1 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -872,9 +872,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1394,9 +1394,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1416,9 +1416,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
-- 
2.24.1



  [application/octet-stream] v6-0001-Rework-query-relation-permission-checking.patch (150.6K, ../../CA+HiwqFVvMm7UrRt7yL8MngBS0hDcCmd5DKp+jPv=HDaJsd5=w@mail.gmail.com/3-v6-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From a833208d6b0b4d9c87b95943db11adb9ccd71f95 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v6 1/2] Rework query relation permission checking

Currently, any information about the permissions to be checked is
stored in query's range table entries.  Only the permissions of
RTE_RELATION entries need be checked, that too only for the relations
that are directly mentioned in the query, not those added afterwards,
say, due to expanding inheritance.  This arrangement means that the
executor must wade through the range table to find those entries that
need their permissions checked, which can be severely wasteful when
there are many entries that belong to inheritance child tables whose
permissions need not be checked.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  Also, RTEs get a new Index field 'perminfoindex' to store the
index in the aforementioned list of the RelPermissionInfo belonging
to the RTE.

The list is initialized by the parser when initializing the range
table.  The rewriter can add more entries to it as rules/views are
expanded.  Finally, the planner combines the lists of the individual
subqueries into one flat list that is passed down to the executor.
---
 contrib/postgres_fdw/postgres_fdw.c       |  81 ++++--
 contrib/sepgsql/dml.c                     |  42 ++-
 contrib/sepgsql/hooks.c                   |   6 +-
 src/backend/access/common/attmap.c        |  13 +-
 src/backend/access/common/tupconvert.c    |   2 +-
 src/backend/catalog/partition.c           |   3 +-
 src/backend/commands/copy.c               |  18 +-
 src/backend/commands/copyfrom.c           |   9 +
 src/backend/commands/indexcmds.c          |   3 +-
 src/backend/commands/tablecmds.c          |  24 +-
 src/backend/commands/view.c               |   4 -
 src/backend/executor/execMain.c           | 105 ++++----
 src/backend/executor/execParallel.c       |   1 +
 src/backend/executor/execPartition.c      |  12 +-
 src/backend/executor/execUtils.c          | 159 ++++++++----
 src/backend/nodes/copyfuncs.c             |  32 ++-
 src/backend/nodes/equalfuncs.c            |  17 +-
 src/backend/nodes/outfuncs.c              |  29 ++-
 src/backend/nodes/readfuncs.c             |  23 +-
 src/backend/optimizer/plan/createplan.c   |   6 +-
 src/backend/optimizer/plan/planner.c      |   6 +
 src/backend/optimizer/plan/setrefs.c      | 123 +++------
 src/backend/optimizer/plan/subselect.c    |   5 +
 src/backend/optimizer/prep/prepjointree.c |  10 +
 src/backend/optimizer/util/inherit.c      | 170 +++++++++----
 src/backend/optimizer/util/relnode.c      |   9 +-
 src/backend/parser/analyze.c              |  62 +++--
 src/backend/parser/parse_clause.c         |   9 +-
 src/backend/parser/parse_relation.c       | 296 ++++++++++++++++------
 src/backend/parser/parse_target.c         |  19 +-
 src/backend/parser/parse_utilcmd.c        |   9 +-
 src/backend/replication/logical/worker.c  |  13 +-
 src/backend/rewrite/rewriteDefine.c       |  15 +-
 src/backend/rewrite/rewriteHandler.c      | 175 ++++++-------
 src/backend/rewrite/rowsecurity.c         |  24 +-
 src/backend/statistics/extended_stats.c   |   7 +-
 src/backend/utils/adt/ri_triggers.c       |  34 +--
 src/backend/utils/adt/selfuncs.c          |  19 +-
 src/include/access/attmap.h               |   6 +-
 src/include/commands/copyfrom_internal.h  |   3 +-
 src/include/executor/executor.h           |   7 +-
 src/include/nodes/execnodes.h             |   9 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/parsenodes.h            |  87 ++++---
 src/include/nodes/pathnodes.h             |   5 +-
 src/include/nodes/plannodes.h             |   5 +
 src/include/optimizer/inherit.h           |   1 +
 src/include/optimizer/planner.h           |   1 +
 src/include/parser/parse_node.h           |   6 +-
 src/include/parser/parse_relation.h       |   3 +
 src/include/rewrite/rewriteHandler.h      |   2 +-
 51 files changed, 1082 insertions(+), 648 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index fa9a099f13..0ef3b11f1d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -458,7 +460,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -623,7 +626,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -657,12 +659,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermisssions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1515,16 +1517,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermisssions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1804,7 +1805,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1883,6 +1885,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte, false);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1894,6 +1925,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1901,6 +1933,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1924,6 +1957,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1935,7 +1969,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2127,6 +2162,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2204,6 +2240,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2220,7 +2258,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2606,7 +2645,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2626,13 +2664,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3930,12 +3967,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3947,12 +3984,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 1f96e8b507..44ec89840f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 19a3ffb7ff..036a4c97f2 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 32405f8610..85221ada52 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 64f54393f3..f5624eeab9 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 790f4ccb92..ffc45efb32 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f4853141..522b800efe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a1..81b037ccbb 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -654,6 +654,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1384,7 +1390,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8d3104821e..3bb7688242 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1230,7 +1230,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bf42587e38..fa0d3a112b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1175,7 +1175,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9601,7 +9602,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9749,7 +9751,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9939,7 +9942,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	table_close(pg_constraint, RowShareLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10092,7 +10096,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12009,7 +12014,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -17740,7 +17746,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18645,7 +18652,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..5bfa730e8a 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -381,10 +381,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..5bde876b78 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -73,7 +73,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -89,8 +89,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -552,8 +552,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,38 +565,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -604,32 +605,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermisssions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -663,14 +653,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -695,15 +685,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -711,12 +701,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -771,17 +761,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -813,9 +800,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1773,7 +1761,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1858,7 +1846,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1910,7 +1899,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2017,7 +2007,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f8a4a40e7b..6c932b8261 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..f4456a1aca 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -574,7 +574,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -631,7 +632,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -773,7 +775,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -1040,7 +1043,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 4ab1302313..9d2113488c 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte, false);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index df0b747883..154d452f39 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -97,6 +97,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -780,6 +781,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1270,6 +1272,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2462,6 +2483,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(perminfoindex);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
@@ -2487,12 +2509,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3178,6 +3194,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5151,6 +5168,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index cb7ddd463c..bb3817ba7d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -992,6 +992,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -2764,6 +2765,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(perminfoindex);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
@@ -2789,17 +2791,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -3838,6 +3848,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 91a89b6d51..79991a19c5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -315,6 +315,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -709,6 +710,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -997,6 +999,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2273,6 +2290,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3077,6 +3095,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3256,6 +3275,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3309,12 +3329,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -3998,6 +4012,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 76cff8a2b1..59f49ef8b0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1442,6 +1443,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -1505,13 +1507,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_INT_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
+	READ_INT_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1590,6 +1603,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2075,6 +2089,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -2849,6 +2864,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RANGETBLENTRY", 13))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f12660a260..d86a585a03 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4056,6 +4056,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5704,7 +5707,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526..ee49ac4b3e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5926,6 +5930,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6053,6 +6058,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6ccec759bd..54178305ce 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,9 +104,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -259,7 +257,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -345,10 +343,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down any subquery
+ * RTEs of the current plan level that did not make into the plan tree by way
+ * of being pulled up, nor turned into SubqueryScan nodes referenced in the
+ * plan tree.  Such subqueries would not thus have had any RelPermissionInfos
+ * referenced in them merged into root->parse->relpermlist, so we must force-
+ * add them to glob->finalrelpermlist.  Failing to do so would prevent the
+ * executor from performing expected permission checks for tables mentioned in
+ * such subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -361,29 +366,13 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
-	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -400,73 +389,27 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					glob->finalrelpermlist =
+						list_concat(glob->finalrelpermlist,
+									rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
-
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
-
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally, add the query's RelPermissionInfos to the global list. */
+	glob->finalrelpermlist = list_concat(glob->finalrelpermlist,
+										 root->parse->relpermlist);
 }
 
 /*
@@ -475,9 +418,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
@@ -488,6 +429,14 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
 	memcpy(newrte, rte, sizeof(RangeTblEntry));
 
+	/*
+	 * Executor may need to look up RelPermissionInfos, so must keep
+	 * perminfoindex around.  Though, the list containing the RelPermissionInfo
+	 * to which the index points will be folded into glob->finalrelpermlist, so
+	 * offset the index likewise.
+	 */
+	newrte->perminfoindex += list_length(glob->finalrelpermlist);
+
 	/* zap unneeded sub-structure */
 	newrte->tablesample = NULL;
 	newrte->subquery = NULL;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..13bfc8f84d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1505,6 +1505,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/*
+	 * Add subquery's RelPermissionInfos into the upper query.
+	 */
+	MergeRelPermissionInfos(parse, subselect->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 387a35e112..de70bcf498 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1131,6 +1131,11 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/*
+	 * Add subquery's RelPermissionInfos into the upper query.
+	 */
+	MergeRelPermissionInfos(parse, subquery->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1269,6 +1274,11 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/*
+	 * Add the child query's RelPermissionInfos into the parent query.
+	 */
+	MergeRelPermissionInfos(root->parse, subquery->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index c758172efa..1e3177fd8a 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 47769cea45..bbfd5c0fca 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* otherrels use the root parent's value. */
+		rel->userid = parent ? parent->userid :
+			GetRelPermissionInfo(root->parse->relpermlist,
+								 rte, false)->checkAsUser;
+	}
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1e..c3298e8625 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -475,6 +475,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -507,7 +508,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -626,6 +627,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -851,7 +859,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -867,8 +875,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -895,6 +903,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1053,8 +1062,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1348,6 +1355,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1582,6 +1590,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1828,6 +1837,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2299,6 +2309,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2365,6 +2376,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2383,7 +2395,7 @@ static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2395,7 +2407,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2436,8 +2448,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2724,6 +2736,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3202,9 +3215,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte,
+														false);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3260,9 +3281,18 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist,
+														 rte, false);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 078029ba1f..ad9717fffe 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3241,16 +3241,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index c5c3f26ecf..911de4ea7e 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1010,10 +1010,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte, false);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1224,10 +1227,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1263,6 +1269,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1406,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1442,7 +1450,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize acesss permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1451,12 +1459,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1470,7 +1480,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1507,6 +1517,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1530,7 +1541,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1539,12 +1550,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1558,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1628,21 +1641,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1935,20 +1942,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1960,7 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2006,20 +2006,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2094,19 +2087,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2185,19 +2172,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,6 +2193,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2335,19 +2317,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2461,16 +2437,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2482,7 +2455,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3102,6 +3075,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3119,7 +3093,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3665,3 +3642,166 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless there already
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfo(*relpermlist, rte, true);
+	if (perminfo)
+	{
+		Assert(rte->perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = rte->relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Remember the index in the RTE. */
+	Assert(rte->perminfoindex == 0);
+	rte->perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Tries to find a RelPermissionInfo for given relation in the provided
+ *		list, erroring out or returning NULL (depending on missing_ok) if not
+ *		found
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte, bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/*
+	 * No need to scan the list on OID if the RTE contains a valid index,
+	 * which is true in most cases except when MergeRelPermissionInfos() calls
+	 * us (via AddRelPermissionInfo() that is).  MergeRelPermissionInfos()
+	 * intentionally resets the original index because it could potentially
+	 * point into a different list than what we are passed, which is possible,
+	 * for example, for RTEs that were just pulled up from another subquery.
+	 * In that case, we force scanning the list by OID and reassign the index
+	 * if an entry is found.
+	 */
+	if (rte->perminfoindex > 0)
+	{
+		if (rte->perminfoindex > list_length(relpermlist))
+			elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+				 rte->relid);
+
+		perminfo = (RelPermissionInfo *) list_nth(relpermlist,
+												  rte->perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		if (rte->relid != perminfo->relid)
+			elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+				 rte->perminfoindex, perminfo->relid, rte->relid);
+
+		return perminfo;
+	}
+	else
+	{
+		ListCell *lc;
+		int		i = 0;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == rte->relid)
+			{
+				/* And set the index in RTE. */
+				rte->perminfoindex = i + 1;
+				return perminfo;
+			}
+			i++;
+		}
+
+		if (!missing_ok)
+			elog(ERROR, "permission info of relation %u not found", rte->relid);
+	}
+
+	return NULL;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source subquery given in
+ *		src_relpermlist into dest_query, "merging" the contents of any that
+ *		are present in both.
+ *
+ * This assumes that the caller has already pulled up the source subquery's
+ * RTEs into dest_query's rtable, because their perminfoindex would need to
+ * be updated to reflect their now belonging in the new mereged list.
+ */
+void
+MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		ListCell *l1;
+
+		foreach(l1, dest_query->rtable)
+		{
+			RangeTblEntry *dest_rte = (RangeTblEntry *) lfirst(l1);
+			RelPermissionInfo *dest_perminfo;
+
+			/*
+			 * Only RELATIONs have a RelPermissionInfo.  Also ignore any that
+			 * don't match the RelPermissionInfo we're trying to merge.
+			 */
+			if (dest_rte->rtekind != RTE_RELATION ||
+				dest_rte->relid != src_perminfo->relid)
+				continue;
+
+			/*
+			 * Reset the index to signal to GetRelPermissionInfo() to re-
+			 * assign the index by looking up an existing entry for the OID in
+			 * dest_query->relpermlist.
+			 */
+			dest_rte->perminfoindex = 0;
+			dest_perminfo = AddRelPermissionInfo(&dest_query->relpermlist,
+												 dest_rte);
+			/* "merge" proprties. */
+			dest_perminfo->inh = src_perminfo->inh;
+			dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+			if (!OidIsValid(dest_perminfo->checkAsUser))
+				dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+			dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+													src_perminfo->selectedCols);
+			dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+													src_perminfo->insertedCols);
+			dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+												   src_perminfo->updatedCols);
+			dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+														src_perminfo->extraUpdatedCols);
+		}
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 9ce3a0de96..fc58425693 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1375,6 +1375,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1389,7 +1390,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1421,12 +1425,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2d857a301b..3bbf005e71 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3010,9 +3011,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3068,6 +3066,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3110,8 +3109,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2e79302a48..6faafaca67 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -155,6 +155,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -463,6 +464,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1668,7 +1671,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	if (handle_streamed_transaction(LOGICAL_REP_MSG_UPDATE, s))
@@ -1711,7 +1714,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1721,14 +1724,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6589345ac4..5ffcb58306 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -821,18 +822,20 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 9521e81100..9c8062cd42 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,23 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(sub_action, parsetree->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1590,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1619,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1703,8 +1706,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1745,18 +1747,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1843,28 +1833,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1893,8 +1864,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte, false);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3035,6 +3010,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3170,6 +3148,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte,
+										 false);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3242,51 +3222,53 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * Mark the new target RTE for the permissions checks that we want to
+	 * Mark the new target relation for the permissions checks that we want to
 	 * enforce against the view owner, as distinct from the query caller.  At
 	 * the relation level, require the same INSERT/UPDATE/DELETE permissions
-	 * that the query caller needs against the view.  We drop the ACL_SELECT
-	 * bit that is presumably in new_rte->requiredPerms initially.
+	 * that the query caller needs against the view.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
-	new_rte->checkAsUser = view->rd_rel->relowner;
-	new_rte->requiredPerms = view_rte->requiredPerms;
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte,
+										 false);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
+	new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3390,7 +3372,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3401,8 +3383,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3676,6 +3656,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3687,6 +3668,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry,
+										   false);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3772,7 +3755,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_DELETE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e10f94904e..f9d0691925 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte, false);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -241,7 +247,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * ALL or SELECT USING policy.
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -284,7 +290,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -340,7 +346,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -369,7 +375,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -386,8 +392,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 69ca52094f..6bca5a74a2 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -30,6 +30,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1559,6 +1560,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1606,10 +1608,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 8ebb2a50a1..348ffbbd55 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1361,8 +1361,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1380,32 +1380,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1415,9 +1409,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb287..708dd121f6 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5135,7 +5135,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ?
+									onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5187,7 +5188,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ?
+											onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5271,7 +5273,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ?
+						onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5319,7 +5322,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ?
+								onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5380,6 +5384,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5388,7 +5393,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ?
+				onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5457,7 +5463,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ?
+					onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 778fa27fd1..f3ce859395 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 4d68d9cceb..752e204ec7 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index cd57a704ad..43a28a30e1 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -598,6 +599,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ddc3529332..186a85a725 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -523,6 +523,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 } ResultRelInfo;
@@ -564,6 +572,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7c657c1241..b25437dd4c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4c5a8a39bf..4ec1237a23 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -145,6 +145,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
@@ -946,37 +948,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1030,11 +1001,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1154,14 +1131,58 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 324d92880b..386f3f7f80 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat list of RelPermissionInfo "*/
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -730,7 +732,8 @@ typedef struct RelOptInfo
 
 	/* Information about foreign tables and foreign joins */
 	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
+	Oid			userid;			/* identifies user to check access as; set
+								 * in non-foreign table relations too! */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index be3c30704a..e64a636813 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
+#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -65,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -636,6 +640,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index e9472f2f73..1ec96d89bd 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 9a15de5025..5b884ad96f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
 #endif							/* PLANNER_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index ee179082ce..e840ebf2bb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -233,7 +235,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -267,6 +270,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..15b4c9b03c 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,8 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte, bool missing_ok);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 728a60c0b0..26300cc143 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-03-23 07:03  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 3 replies; 73+ messages in thread

From: Amit Langote @ 2022-03-23 07:03 UTC (permalink / raw)
  To: Zhihong Yu <[email protected]>; +Cc: Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Mar 14, 2022 at 4:36 PM Amit Langote <[email protected]> wrote:
> Also needed fixes when rebasing.

Needed another rebase.

As the changes being made with the patch are non-trivial and the patch
hasn't been reviewed very significantly since Alvaro's comments back
in Sept 2021 which I've since addressed, I'm thinking of pushing this
one into the version 16 dev cycle.

-- 
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v10-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (116.2K, ../../CA+HiwqH5BDpcdOkiPH8FqoDL6Lg74Z-yyO2_W_H-RcqpZYoaew@mail.gmail.com/2-v10-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 8b1fe9b7c2aec0e072f24c2952c57b242c96b3c7 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v10 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add an RTE for view relations.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  31 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 210 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 696 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 25 files changed, 708 insertions(+), 775 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f210f91188..005f30299d 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2485,7 +2485,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2548,7 +2548,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6403,10 +6403,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6497,10 +6497,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 160c709044..2519970914 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -339,78 +339,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -573,12 +501,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index f81c63aa23..e1cd9d0832 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1706,7 +1706,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1824,17 +1825,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fd1052e5db..af7a6a7e43 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2154,7 +2154,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2170,7 +2170,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2186,7 +2186,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2202,7 +2202,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2220,7 +2220,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3113,7 +3113,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 0a23a39aa2..81d87a2678 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1545,7 +1545,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1597,7 +1597,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1613,7 +1613,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1629,7 +1629,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2114,15 +2114,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index aabc564e2c..a2322ebeec 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2475,8 +2475,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2487,8 +2487,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2514,8 +2514,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2527,8 +2527,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 32385bbb0e..e60bdf2dff 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1604,9 +1604,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1627,9 +1627,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1719,8 +1719,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1965,7 +1965,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2017,19 +2017,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 6a56f0b09c..d3e7b23332 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 2334a1321e..2c4da34687 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6cb6388880..f8cb7d6f94 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,50 +1302,50 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1358,22 +1358,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1413,13 +1413,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1437,10 +1437,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1686,23 +1686,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1716,10 +1716,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1787,13 +1787,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1806,57 +1806,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1879,8 +1879,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1889,13 +1889,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2041,26 +2041,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2079,41 +2079,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2123,68 +2123,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2201,19 +2201,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2274,64 +2274,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname, t.oid, x.indexrelid;
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2516,24 +2516,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3053,7 +3053,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3092,8 +3092,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3105,8 +3105,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3118,8 +3118,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3131,8 +3131,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index d3e02ca63b..00ff5bbfe6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index bb9ff7f07b..5e4612fff1 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index ff76ad4d6e..f6f78ec52d 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -872,9 +872,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1394,9 +1394,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1416,9 +1416,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 55b65ef324..bb994a05de 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 0484260281..974b14f859 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.24.1



  [application/octet-stream] v10-0001-Rework-query-relation-permission-checking.patch (151.4K, ../../CA+HiwqH5BDpcdOkiPH8FqoDL6Lg74Z-yyO2_W_H-RcqpZYoaew@mail.gmail.com/3-v10-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 207241cda01de936bc8f8a624a2c3b486975feb6 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v10 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table to look for any
RTE_RELATION entries to have permissions checked.  This arrangement
makes permissions-checking needlessly expensive when many inheritance
children are added to the range range, because while their permissions
need not be checked, the only way to find that out is by seeing
requiredPerms == 0 in the their RTEs.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RelPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the entry's index in the query's list of
RelPermissionInfos.
---
 contrib/postgres_fdw/postgres_fdw.c         |  81 ++++--
 contrib/sepgsql/dml.c                       |  42 ++-
 contrib/sepgsql/hooks.c                     |   6 +-
 src/backend/access/common/attmap.c          |  13 +-
 src/backend/access/common/tupconvert.c      |   2 +-
 src/backend/catalog/partition.c             |   3 +-
 src/backend/commands/copy.c                 |  18 +-
 src/backend/commands/copyfrom.c             |   9 +
 src/backend/commands/indexcmds.c            |   3 +-
 src/backend/commands/tablecmds.c            |  24 +-
 src/backend/commands/view.c                 |   4 -
 src/backend/executor/execMain.c             | 105 ++++---
 src/backend/executor/execParallel.c         |   1 +
 src/backend/executor/execPartition.c        |  12 +-
 src/backend/executor/execUtils.c            | 159 +++++++----
 src/backend/nodes/copyfuncs.c               |  32 ++-
 src/backend/nodes/equalfuncs.c              |  17 +-
 src/backend/nodes/outfuncs.c                |  29 +-
 src/backend/nodes/readfuncs.c               |  23 +-
 src/backend/optimizer/plan/createplan.c     |   6 +-
 src/backend/optimizer/plan/planner.c        |   6 +
 src/backend/optimizer/plan/setrefs.c        | 123 +++------
 src/backend/optimizer/plan/subselect.c      |   5 +
 src/backend/optimizer/prep/prepjointree.c   |  10 +
 src/backend/optimizer/util/inherit.c        | 170 ++++++++----
 src/backend/optimizer/util/relnode.c        |   9 +-
 src/backend/parser/analyze.c                |  62 +++--
 src/backend/parser/parse_clause.c           |   9 +-
 src/backend/parser/parse_relation.c         | 289 ++++++++++++++------
 src/backend/parser/parse_target.c           |  19 +-
 src/backend/parser/parse_utilcmd.c          |   9 +-
 src/backend/replication/logical/worker.c    |  13 +-
 src/backend/replication/pgoutput/pgoutput.c |   2 +-
 src/backend/rewrite/rewriteDefine.c         |  15 +-
 src/backend/rewrite/rewriteHandler.c        | 182 ++++++------
 src/backend/rewrite/rowsecurity.c           |  24 +-
 src/backend/statistics/extended_stats.c     |   7 +-
 src/backend/utils/adt/ri_triggers.c         |  34 +--
 src/backend/utils/adt/selfuncs.c            |  19 +-
 src/include/access/attmap.h                 |   6 +-
 src/include/commands/copyfrom_internal.h    |   3 +-
 src/include/executor/executor.h             |   7 +-
 src/include/nodes/execnodes.h               |   9 +
 src/include/nodes/nodes.h                   |   1 +
 src/include/nodes/parsenodes.h              |  87 +++---
 src/include/nodes/pathnodes.h               |   5 +-
 src/include/nodes/plannodes.h               |   5 +
 src/include/optimizer/inherit.h             |   1 +
 src/include/optimizer/planner.h             |   1 +
 src/include/parser/parse_node.h             |   6 +-
 src/include/parser/parse_relation.h         |   3 +
 src/include/rewrite/rewriteHandler.h        |   2 +-
 52 files changed, 1080 insertions(+), 652 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 56654844e8..a33191de40 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -458,7 +460,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -623,7 +626,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -657,12 +659,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermisssions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1515,16 +1517,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermisssions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1816,7 +1817,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1895,6 +1897,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte, false);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1906,6 +1937,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1913,6 +1945,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1936,6 +1969,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1947,7 +1981,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2139,6 +2174,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2216,6 +2252,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2232,7 +2270,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2618,7 +2657,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2638,13 +2676,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3942,12 +3979,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3959,12 +3996,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..2cf75b6a6d 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..cb9aeb175d 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..7bc85d9eb5 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7da7105d44..44b86cdedf 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index db6eb6fae7..9e42904c6b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1383,7 +1389,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba..356a93f7c5 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1231,7 +1231,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 80faae985e..8b5bb547fa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1200,7 +1200,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9625,7 +9626,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9792,7 +9794,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9998,7 +10001,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10176,7 +10180,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12270,7 +12275,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18021,7 +18027,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18948,7 +18955,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c6..160c709044 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -397,10 +397,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 473d2e00a2..076933b513 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,7 +74,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -553,8 +553,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -566,38 +566,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -605,32 +606,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermisssions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -664,14 +654,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -696,15 +686,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -712,12 +702,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -772,17 +762,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -814,9 +801,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1855,7 +1843,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1940,7 +1928,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1992,7 +1981,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2099,7 +2089,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 5dd8ab7db2..8b61aebe88 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 90ed1485d1..981d7d3111 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -574,7 +574,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -631,7 +632,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -773,7 +775,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -1040,7 +1043,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..732f833120 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte, false);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d4f8455a2b..48506a2f5e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -97,6 +97,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -781,6 +782,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1271,6 +1273,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2463,6 +2484,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(perminfoindex);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
@@ -2488,12 +2510,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3183,6 +3199,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5178,6 +5195,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f1002afe7a..c499611316 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -992,6 +992,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -2775,6 +2776,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(perminfoindex);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
@@ -2800,17 +2802,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -3863,6 +3873,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c7..655eb2d888 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -315,6 +315,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -710,6 +711,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -998,6 +1000,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2274,6 +2291,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3079,6 +3097,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3258,6 +3277,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3311,12 +3331,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -4009,6 +4023,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..f994cd9908 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -264,6 +264,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1440,6 +1441,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -1503,13 +1505,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_INT_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
+	READ_INT_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1588,6 +1601,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2074,6 +2088,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -2848,6 +2863,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RANGETBLENTRY", 13))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index fa069a217c..3097d38d42 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4093,6 +4093,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5743,7 +5746,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea..705afea0d6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5926,6 +5930,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6053,6 +6058,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index a7b11b7f03..bdf8b66e4d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,9 +104,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -259,7 +257,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -345,10 +343,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down any subquery
+ * RTEs of the current plan level that did not make into the plan tree by way
+ * of being pulled up, nor turned into SubqueryScan nodes referenced in the
+ * plan tree.  Such subqueries would not thus have had any RelPermissionInfos
+ * referenced in them merged into root->parse->relpermlist, so we must force-
+ * add them to glob->finalrelpermlist.  Failing to do so would prevent the
+ * executor from performing expected permission checks for tables mentioned in
+ * such subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -361,29 +366,13 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
-	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -400,73 +389,27 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					glob->finalrelpermlist =
+						list_concat(glob->finalrelpermlist,
+									rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
-
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
-
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally, add the query's RelPermissionInfos to the global list. */
+	glob->finalrelpermlist = list_concat(glob->finalrelpermlist,
+										 root->parse->relpermlist);
 }
 
 /*
@@ -475,9 +418,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
@@ -488,6 +429,14 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
 	memcpy(newrte, rte, sizeof(RangeTblEntry));
 
+	/*
+	 * Executor may need to look up RelPermissionInfos, so must keep
+	 * perminfoindex around.  Though, the list containing the RelPermissionInfo
+	 * to which the index points will be folded into glob->finalrelpermlist, so
+	 * offset the index likewise.
+	 */
+	newrte->perminfoindex += list_length(glob->finalrelpermlist);
+
 	/* zap unneeded sub-structure */
 	newrte->tablesample = NULL;
 	newrte->subquery = NULL;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 863e0e24a1..c0b198b4c9 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1507,6 +1507,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/*
+	 * Add subquery's RelPermissionInfos into the upper query.
+	 */
+	MergeRelPermissionInfos(parse, subselect->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 74823e8437..c1f9610478 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1131,6 +1131,11 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/*
+	 * Add subquery's RelPermissionInfos into the upper query.
+	 */
+	MergeRelPermissionInfos(parse, subquery->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1269,6 +1274,11 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/*
+	 * Add the child query's RelPermissionInfos into the parent query.
+	 */
+	MergeRelPermissionInfos(root->parse, subquery->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f3..4da2b213ed 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..cd70026f3e 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* otherrels use the root parent's value. */
+		rel->userid = parent ? parent->userid :
+			GetRelPermissionInfo(root->parse->relpermlist,
+								 rte, false)->checkAsUser;
+	}
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 61026753a3..c1f290e578 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -516,6 +516,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -548,7 +549,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -667,6 +668,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -892,7 +900,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -908,8 +916,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -936,6 +944,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1094,8 +1103,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1389,6 +1396,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1623,6 +1631,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1869,6 +1878,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2340,6 +2350,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2406,6 +2417,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2424,7 +2436,7 @@ static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2436,7 +2448,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2477,8 +2489,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2765,6 +2777,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3243,9 +3256,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte,
+														false);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3301,9 +3322,18 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist,
+														 rte, false);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index d8b14ba7cd..7c10f262e0 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3241,16 +3241,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index cb9e177b5e..f9ea9400c4 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1010,10 +1010,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte, false);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1224,10 +1227,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1263,6 +1269,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1406,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1442,7 +1450,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize acesss permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1451,12 +1459,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1470,7 +1480,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1507,6 +1517,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1530,7 +1541,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1539,12 +1550,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh = inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1558,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1628,21 +1641,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1935,20 +1942,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1960,7 +1960,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2006,20 +2006,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2094,19 +2087,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2185,19 +2172,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,6 +2193,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2335,19 +2317,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2461,16 +2437,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2482,7 +2455,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3102,6 +3075,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3119,7 +3093,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3665,3 +3642,159 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless there already
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfo(*relpermlist, rte, true);
+	if (perminfo)
+	{
+		Assert(rte->perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = rte->relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Remember the index in the RTE. */
+	Assert(rte->perminfoindex == 0);
+	rte->perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Tries to find a RelPermissionInfo for given relation in the provided
+ *		list, erroring out or returning NULL (depending on missing_ok) if not
+ *		found
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte, bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+	ListCell   *lc;
+	int			i;
+
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/* Easy when the RTE already knows. */
+	if (rte->perminfoindex > 0)
+	{
+		if (rte->perminfoindex > list_length(relpermlist))
+			elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+				 rte->relid);
+
+		perminfo = (RelPermissionInfo *) list_nth(relpermlist,
+												  rte->perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		if (rte->relid != perminfo->relid)
+			elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+				 rte->perminfoindex, perminfo->relid, rte->relid);
+
+		return perminfo;
+	}
+
+	/* Looks like the RTE doesn't, so try to find it the hard way. */
+	i = 0;
+	foreach(lc, relpermlist)
+	{
+		perminfo = (RelPermissionInfo *) lfirst(lc);
+		if (perminfo->relid == rte->relid)
+		{
+			/* And set the index in RTE. */
+			rte->perminfoindex = i + 1;
+			return perminfo;
+		}
+		i++;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", rte->relid);
+
+	return NULL;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source subquery given in
+ *		src_relpermlist into dest_query, "merging" the contents of any that
+ *		are present in both.
+ *
+ * This assumes that the caller has already pulled up the source subquery's
+ * RTEs into dest_query's rtable, because their perminfoindex would need to
+ * be updated to reflect their now belonging in the new mereged list.
+ */
+void
+MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		ListCell *l1;
+
+		foreach(l1, dest_query->rtable)
+		{
+			RangeTblEntry *dest_rte = (RangeTblEntry *) lfirst(l1);
+			RelPermissionInfo *dest_perminfo;
+
+			/*
+			 * Only RELATIONs have a RelPermissionInfo.  Also ignore any that
+			 * don't match the RelPermissionInfo we're trying to merge.
+			 */
+			if (dest_rte->rtekind != RTE_RELATION ||
+				dest_rte->relid != src_perminfo->relid)
+				continue;
+
+			/*
+			 * The current value of perminfoindex points to a perminfo in the
+			 * source query's relpermlist, which we're trying to merge with
+			 * the destination query's relpermlist.  So, reset the index to
+			 * signal to GetRelPermissionInfo(), that gets called via
+			 * AddRelPermissionInfo(), to assign it the index of an entry with
+			 * the same relid in dest_query->relpermlist.
+			 */
+			dest_rte->perminfoindex = 0;
+			dest_perminfo = AddRelPermissionInfo(&dest_query->relpermlist,
+												 dest_rte);
+			/* "merge" proprties. */
+			dest_perminfo->inh = src_perminfo->inh;
+			dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+			if (!OidIsValid(dest_perminfo->checkAsUser))
+				dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+			dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+													src_perminfo->selectedCols);
+			dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+													src_perminfo->insertedCols);
+			dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+												   src_perminfo->updatedCols);
+			dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+														src_perminfo->extraUpdatedCols);
+		}
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 059eeb9e94..6ab35e8a16 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1375,6 +1375,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1389,7 +1390,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1421,12 +1425,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cd946c7692..e6117823dd 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3013,9 +3014,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3071,6 +3069,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3113,8 +3112,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 82dcffc2db..3e1c89a7d9 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -491,6 +492,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1778,7 +1781,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1826,7 +1829,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1836,14 +1839,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 5fddab3a3d..ff07b26d29 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -899,7 +899,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..1a9e5c8524 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -821,18 +822,20 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 4eeed580b1..f81c63aa23 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,23 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(sub_action, parsetree->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1590,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1619,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1703,8 +1706,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1745,18 +1747,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1843,28 +1833,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1893,8 +1864,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte, false);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3035,6 +3010,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3170,6 +3148,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte,
+										 false);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3242,57 +3222,60 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte,
+										 false);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3396,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3407,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3682,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3693,6 +3675,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry,
+										   false);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3778,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_DELETE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index f0a046d65a..03f3881997 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte, false);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -241,7 +247,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * ALL or SELECT USING policy.
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -284,7 +290,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -340,7 +346,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -369,7 +375,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -386,8 +392,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ca48395d5c..cc68e68f1d 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1560,6 +1561,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1607,10 +1609,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 01d4c22cfc..4590e1148c 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1367,8 +1367,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1386,32 +1386,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1421,9 +1415,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1fbb0b28c3..934e21eda4 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5137,7 +5137,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ?
+									onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5189,7 +5190,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ?
+											onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5268,7 +5270,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ?
+						onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5316,7 +5319,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ?
+								onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5377,6 +5381,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5385,7 +5390,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ?
+				onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5454,7 +5460,8 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ?
+					onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 3df1c5a97c..af40f21496 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 82925b4b63..4dcd7a6051 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 44dd73fc80..b13709c859 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -528,6 +528,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -575,6 +583,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c34..a422955463 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2f618cb229..1151d39e96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -147,6 +147,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
 	List	   *targetList;		/* target list (of TargetEntry) */
@@ -949,37 +951,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1033,11 +1004,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1157,14 +1134,58 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fe..4b8ce415a1 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat list of RelPermissionInfo "*/
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -730,7 +732,8 @@ typedef struct RelOptInfo
 
 	/* Information about foreign tables and foreign joins */
 	Oid			serverid;		/* identifies server for the table or join */
-	Oid			userid;			/* identifies user to check access as */
+	Oid			userid;			/* identifies user to check access as; set
+								 * in non-foreign table relations too! */
 	bool		useridiscurrent;	/* join is only valid for current user */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b518ce6b2..0abc993369 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
+#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -65,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -651,6 +655,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index eb96b2cfc1..6b612cc37e 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
 #endif							/* PLANNER_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 8c859d0d0e..34f101077a 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -180,6 +180,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -233,7 +235,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -267,6 +270,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 06dc27995b..6168512b99 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,8 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte, bool missing_ok);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..f21786da35 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-03-23 09:49  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  2 siblings, 0 replies; 73+ messages in thread

From: Alvaro Herrera @ 2022-03-23 09:49 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-Mar-23, Amit Langote wrote:

> As the changes being made with the patch are non-trivial and the patch
> hasn't been reviewed very significantly since Alvaro's comments back
> in Sept 2021 which I've since addressed, I'm thinking of pushing this
> one into the version 16 dev cycle.

Let's not get ahead of ourselves.  The commitfest is not yet over.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"La virtud es el justo medio entre dos defectos" (Aristóteles)





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-03-23 21:02  Zhihong Yu <[email protected]>
  parent: Amit Langote <[email protected]>
  2 siblings, 0 replies; 73+ messages in thread

From: Zhihong Yu @ 2022-03-23 21:02 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Mar 23, 2022 at 12:03 AM Amit Langote <[email protected]>
wrote:

> On Mon, Mar 14, 2022 at 4:36 PM Amit Langote <[email protected]>
> wrote:
> > Also needed fixes when rebasing.
>
> Needed another rebase.
>
> As the changes being made with the patch are non-trivial and the patch
> hasn't been reviewed very significantly since Alvaro's comments back
> in Sept 2021 which I've since addressed, I'm thinking of pushing this
> one into the version 16 dev cycle.
>
> --
> Amit Langote
> EDB: http://www.enterprisedb.com

Hi,
For patch 1:

bq. makes permissions-checking needlessly expensive when many inheritance
children are added to the range range

'range' is repeated in the above sentence.

+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)

Since RelPermissionInfo is for one relation, I think the 'One' in func name
can be dropped.

+       else                    /* this isn't a child result rel */
+           resultRelInfo->ri_RootToChildMap = NULL;
...
+       resultRelInfo->ri_RootToChildMapValid = true;

Should the assignment of true value be moved into the if block (in the else
block, ri_RootToChildMap is assigned NULL) ?

+   /* Looks like the RTE doesn't, so try to find it the hard way. */

doesn't -> doesn't know

Cheers


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-03-24 19:46  David Rowley <[email protected]>
  parent: Amit Langote <[email protected]>
  2 siblings, 1 reply; 73+ messages in thread

From: David Rowley @ 2022-03-24 19:46 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, 23 Mar 2022 at 20:03, Amit Langote <[email protected]> wrote:
>
> On Mon, Mar 14, 2022 at 4:36 PM Amit Langote <[email protected]> wrote:
> > Also needed fixes when rebasing.
>
> Needed another rebase.

I had a look at the v10-0001 patch. I agree that it seems to be a good
idea to separate out the required permission checks rather than having
the Bitmapset to index the interesting range table entries.

One thing that I could just not determine from looking at the patch
was if there's meant to be just 1 RelPermissionInfo per RTE or per rel
Oid. None of the comments helped me understand this and
MergeRelPermissionInfos() seems to exist that appears to try and
uniquify this to some extent.  I just see no code that does this
process for a single query level. I've provided more detail on this in
#5 below.

Here's my complete review of v10-0001:

1. ExecCheckPermisssions -> ExecCheckPermissions

2. I think you'll want to move the userid field away from below a
comment that claims the following fields are for foreign tables only.

  /* Information about foreign tables and foreign joins */
  Oid serverid; /* identifies server for the table or join */
- Oid userid; /* identifies user to check access as */
+ Oid userid; /* identifies user to check access as; set
+ * in non-foreign table relations too! */

3. This should use READ_OID_FIELD()

READ_INT_FIELD(checkAsUser);

and this one:

READ_INT_FIELD(relid);

4.  This looks wrong:

- rel->userid = rte->checkAsUser;
+ if (rte->rtekind == RTE_RELATION)
+ {
+ /* otherrels use the root parent's value. */
+ rel->userid = parent ? parent->userid :
+ GetRelPermissionInfo(root->parse->relpermlist,
+ rte, false)->checkAsUser;
+ }

If 'parent' is false then you'll assign the result of
GetRelPermissionInfo (a RelPermissionInfo *) to an Oid.

5. I'm not sure if there's a case that can break this one, but I'm not
very confident that there's not one:

I'm not sure I agree with how you've coded GetRelPermissionInfo().
You're searching for a RelPermissionInfo based on the table's Oid.  If
you have multiple RelPermissionInfos for the same Oid then this will
just find the first one and return it, but that might not be the one
for the RangeTblEntry in question.

Here's an example of the sort of thing that could have problems with this:

postgres=# create role bob;
CREATE ROLE
postgres=# create table ab (a int, b int);
CREATE TABLE
postgres=# grant all (a) on table ab to bob;
GRANT
postgres=# set session authorization bob;
SET
postgres=> update ab set a = (select b from ab);
ERROR:  permission denied for table ab

The patch does correctly ERROR out here on permission failure, but as
far as I can see, that's just due to the fact that we're checking the
permissions of all items in the PlannedStmt.relpermlist List.  If
there had been code that had tried to find the RelPermissionInfo based
on the relation's Oid then we'd have accidentally found that we only
need an UPDATE permission on (a).  SELECT on (b) wouldn't have been
checked.

As far as I can see, to fix that you'd either need to store the RTI of
the RelPermissionInfo and lookup based on that, or you'd have to
bms_union() all the columns and bitwise OR the required permissions
and ensure you only have 1 RelPermissionInfo per Oid.

The fact that I have two entries when I debug InitPlan() seems to
disagree with what the comment in AddRelPermissionInfo() is claiming
should happen:

/*
* To prevent duplicate entries for a given relation, check if already in
* the list.
*/

I'm not clear on if the list is meant to be unique on Oid or not.

6. acesss?

- * Set flags and access permissions.
+ * Set flags and initialize acesss permissions.

7. I was expecting to see an |= here:

+ /* "merge" proprties. */
+ dest_perminfo->inh = src_perminfo->inh;

Why is a plain assignment ok?

8. Some indentation problems here:

@@ -3170,6 +3148,8 @@ rewriteTargetView(Query *parsetree, Relation view)

  base_rt_index = rtr->rtindex;
  base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte,
+ false);

9. You can use foreach_current_index(lc) + 1 in:

+ i = 0;
+ foreach(lc, relpermlist)
+ {
+ perminfo = (RelPermissionInfo *) lfirst(lc);
+ if (perminfo->relid == rte->relid)
+ {
+ /* And set the index in RTE. */
+ rte->perminfoindex = i + 1;
+ return perminfo;
+ }
+ i++;
+ }

10. I think the double quote is not in the correct place in this comment:

  List    *finalrtable; /* "flat" rangetable for executor */

+ List    *finalrelpermlist; /* "flat list of RelPermissionInfo "*/


11. Looks like an accidental change:

+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,

 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);

+

12. These need to be broken into multiple lines:

+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
RangeTblEntry *rte, bool missing_ok);

David





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-03-31 03:16  Amit Langote <[email protected]>
  parent: David Rowley <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-03-31 03:16 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Mar 25, 2022 at 4:46 AM David Rowley <[email protected]> wrote:
> I had a look at the v10-0001 patch. I agree that it seems to be a good
> idea to separate out the required permission checks rather than having
> the Bitmapset to index the interesting range table entries.

Thanks David for taking a look at this.

> One thing that I could just not determine from looking at the patch
> was if there's meant to be just 1 RelPermissionInfo per RTE or per rel
> Oid.

It's the latter.

> None of the comments helped me understand this

I agree that the comment above the RelPermissionInfo struct definition
missed mentioning this really important bit.  I've tried fixing that
as:

@@ -1142,7 +1142,9 @@ typedef struct RangeTblEntry
  *         Per-relation information for permission checking. Added to the query
  *         by the parser when populating the query range table and subsequently
  *         editorialized on by the rewriter and the planner.  There is an entry
- *         each for all RTE_RELATION entries present in the range table.
+ *         each for all RTE_RELATION entries present in the range table, though
+ *         different RTEs for the same relation share the
RelPermissionInfo, that
+ *         is, there is only one RelPermissionInfo containing a given relid.

> and
> MergeRelPermissionInfos() seems to exist that appears to try and
> uniquify this to some extent.  I just see no code that does this
> process for a single query level. I've provided more detail on this in
> #5 below.
>
> Here's my complete review of v10-0001:
>
> 1. ExecCheckPermisssions -> ExecCheckPermissions

Fixed.

> 2. I think you'll want to move the userid field away from below a
> comment that claims the following fields are for foreign tables only.
>
>   /* Information about foreign tables and foreign joins */
>   Oid serverid; /* identifies server for the table or join */
> - Oid userid; /* identifies user to check access as */
> + Oid userid; /* identifies user to check access as; set
> + * in non-foreign table relations too! */

Actually, I realized that the comment should not have been touched to
begin with.  Reverted.

> 3. This should use READ_OID_FIELD()
>
> READ_INT_FIELD(checkAsUser);
>
> and this one:
>
> READ_INT_FIELD(relid);

Fixed.

> 4.  This looks wrong:
>
> - rel->userid = rte->checkAsUser;
> + if (rte->rtekind == RTE_RELATION)
> + {
> + /* otherrels use the root parent's value. */
> + rel->userid = parent ? parent->userid :
> + GetRelPermissionInfo(root->parse->relpermlist,
> + rte, false)->checkAsUser;
> + }
>
> If 'parent' is false then you'll assign the result of
> GetRelPermissionInfo (a RelPermissionInfo *) to an Oid.

Hmm, I don't see a problem, because what's being assigned is
`GetRelPermissionInfo(...)->checkAsUser`.

Anyway, I rewrote the block more verbosely as:

    if (rte->rtekind == RTE_RELATION)
    {
-       /* otherrels use the root parent's value. */
-       rel->userid = parent ? parent->userid :
-           GetRelPermissionInfo(root->parse->relpermlist,
-                                rte, false)->checkAsUser;
+       /*
+        * Get the userid from the relation's RelPermissionInfo, though
+        * only the tables mentioned in query are assigned RelPermissionInfos.
+        * Child relations (otherrels) simply use the parent's value.
+        */
+       if (parent == NULL)
+       {
+           RelPermissionInfo *perminfo =
+               GetRelPermissionInfo(root->parse->relpermlist, rte, false);
+
+           rel->userid = perminfo->checkAsUser;
+       }
+       else
+           rel->userid = parent->userid;
    }
+   else
+       rel->userid = InvalidOid;

> 5. I'm not sure if there's a case that can break this one, but I'm not
> very confident that there's not one:
>
> I'm not sure I agree with how you've coded GetRelPermissionInfo().
> You're searching for a RelPermissionInfo based on the table's Oid.  If
> you have multiple RelPermissionInfos for the same Oid then this will
> just find the first one and return it, but that might not be the one
> for the RangeTblEntry in question.
>
> Here's an example of the sort of thing that could have problems with this:
>
> postgres=# create role bob;
> CREATE ROLE
> postgres=# create table ab (a int, b int);
> CREATE TABLE
> postgres=# grant all (a) on table ab to bob;
> GRANT
> postgres=# set session authorization bob;
> SET
> postgres=> update ab set a = (select b from ab);
> ERROR:  permission denied for table ab
>
> The patch does correctly ERROR out here on permission failure, but as
> far as I can see, that's just due to the fact that we're checking the
> permissions of all items in the PlannedStmt.relpermlist List.  If
> there had been code that had tried to find the RelPermissionInfo based
> on the relation's Oid then we'd have accidentally found that we only
> need an UPDATE permission on (a).  SELECT on (b) wouldn't have been
> checked.
>
> As far as I can see, to fix that you'd either need to store the RTI of
> the RelPermissionInfo and lookup based on that, or you'd have to
> bms_union() all the columns and bitwise OR the required permissions
> and ensure you only have 1 RelPermissionInfo per Oid.
>
> The fact that I have two entries when I debug InitPlan() seems to
> disagree with what the comment in AddRelPermissionInfo() is claiming
> should happen:
>
> /*
> * To prevent duplicate entries for a given relation, check if already in
> * the list.
> */
>
> I'm not clear on if the list is meant to be unique on Oid or not.

Yeah, it is, but it seems that the code I added in
add_rtes_to_flat_rtable() to accumulate various subplans' relpermlists
into finalrelpermlist didn't actually bother to unique'ify the list.
It used list_concat() to combine finalrelpermlist and a given
subplan's relpermlist, instead of MergeRelPemissionInfos to merge the
two lists.

I've fixed that in the attached and can now see that the plan for the
query in your example ends up with just one RelPermissionInfo which
combines the permissions and column bitmapsets for all operations.

> 6. acesss?
>
> - * Set flags and access permissions.
> + * Set flags and initialize acesss permissions.
>
> 7. I was expecting to see an |= here:
>
> + /* "merge" proprties. */
> + dest_perminfo->inh = src_perminfo->inh;
>
> Why is a plain assignment ok?

You're perhaps right that |= is correct.  I forget the details but I
think I added 'inh' field to RelPemissionInfo to get some tests in
sepgsql contrib module to pass and those tests apparently didn't mind
the current coding.

> 8. Some indentation problems here:
>
> @@ -3170,6 +3148,8 @@ rewriteTargetView(Query *parsetree, Relation view)
>
>   base_rt_index = rtr->rtindex;
>   base_rte = rt_fetch(base_rt_index, viewquery->rtable);
> +base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte,
> + false);

Fixed.

>
> 9. You can use foreach_current_index(lc) + 1 in:
>
> + i = 0;
> + foreach(lc, relpermlist)
> + {
> + perminfo = (RelPermissionInfo *) lfirst(lc);
> + if (perminfo->relid == rte->relid)
> + {
> + /* And set the index in RTE. */
> + rte->perminfoindex = i + 1;
> + return perminfo;
> + }
> + i++;
> + }

Oh, nice tip.  Done.

> 10. I think the double quote is not in the correct place in this comment:
>
>   List    *finalrtable; /* "flat" rangetable for executor */
>
> + List    *finalrelpermlist; /* "flat list of RelPermissionInfo "*/
>
>
> 11. Looks like an accidental change:
>
> +++ b/src/include/optimizer/planner.h
> @@ -58,4 +58,5 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
>
>  extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
>
> +
>
> 12. These need to be broken into multiple lines:
>
> +extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
> RangeTblEntry *rte);
> +extern void MergeRelPermissionInfos(Query *dest_query, List *src_relpermlist);
> +extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
> RangeTblEntry *rte, bool missing_ok);

All fixed.

v11 attached.

--
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v11-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (119.6K, ../../CA+HiwqHkMCrumZC1D7Tbs140djY1gQrq9-k362s-Hqai44X2zA@mail.gmail.com/2-v11-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 6ceefde7fb936324bfb3ae0db5951c0342fc9397 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v11 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add an RTE for view relations.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  34 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 210 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 706 +++++++++---------
 src/test/regress/expected/sqljson.out         |   6 +-
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 716 insertions(+), 795 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f210f91188..005f30299d 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2485,7 +2485,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2548,7 +2548,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6403,10 +6403,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6497,10 +6497,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b97b8b0435..2cbc53b42c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f0958f03a0..2519970914 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -339,78 +339,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -573,12 +501,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fca2a1eaf0..6225ccba30 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1859,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index af5d6fa5a3..a16b29d756 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2153,7 +2153,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2169,7 +2169,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2185,7 +2185,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2201,7 +2201,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2219,7 +2219,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3204,7 +3204,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 0a23a39aa2..81d87a2678 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1545,7 +1545,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1597,7 +1597,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1613,7 +1613,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1629,7 +1629,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2114,15 +2114,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 07473dd660..bc5d230e03 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2475,8 +2475,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2487,8 +2487,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2514,8 +2514,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2527,8 +2527,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 32385bbb0e..e60bdf2dff 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1604,9 +1604,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1627,9 +1627,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1719,8 +1719,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1965,7 +1965,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2017,19 +2017,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 6a56f0b09c..d3e7b23332 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 2334a1321e..2c4da34687 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 423b9b99fb..598cf30ec9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,13 +1419,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1451,10 +1451,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1700,23 +1700,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1730,10 +1730,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1801,13 +1801,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1820,57 +1820,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1893,8 +1893,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1903,13 +1903,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2055,26 +2055,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2093,41 +2093,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2137,68 +2137,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2215,19 +2215,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2237,19 +2237,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2293,64 +2293,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2535,24 +2535,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3072,7 +3072,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3111,8 +3111,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3124,8 +3124,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3137,8 +3137,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3150,8 +3150,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 27dca7815a..564662c9ae 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -683,7 +683,7 @@ SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING t
 FROM generate_series(1,5) i;
 \sv json_objectagg_view
 CREATE OR REPLACE VIEW public.json_objectagg_view AS
- SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg"
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_objectagg_view;
 -- Test JSON_ARRAYAGG deparsing
@@ -719,7 +719,7 @@ SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text
 FROM generate_series(1,5) i;
 \sv json_arrayagg_view
 CREATE OR REPLACE VIEW public.json_arrayagg_view AS
- SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg"
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_arrayagg_view;
 -- Test JSON_ARRAY(subquery) deparsing
@@ -937,7 +937,7 @@ SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT
 \sv is_json_view
 CREATE OR REPLACE VIEW public.is_json_view AS
  SELECT '1'::text IS JSON AS "any",
-    ('1'::text || i.i) IS JSON SCALAR AS scalar,
+    ('1'::text || i) IS JSON SCALAR AS scalar,
     NOT '[]'::text IS JSON ARRAY AS "array",
     '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
    FROM generate_series(1, 3) i(i)
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index cd812336f2..b09603dc98 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index bb9ff7f07b..5e4612fff1 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7c6de7cc07..bea54c91cd 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -872,9 +872,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1394,9 +1394,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1416,9 +1416,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 55b65ef324..bb994a05de 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 0484260281..974b14f859 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.24.1



  [application/octet-stream] v11-0001-Rework-query-relation-permission-checking.patch (158.6K, ../../CA+HiwqHkMCrumZC1D7Tbs140djY1gQrq9-k362s-Hqai44X2zA@mail.gmail.com/3-v11-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 1c6a935264be115573c7a17eb596a1a2e3cff5e3 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v11 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table to look for any
RTE_RELATION entries to have permissions checked.  This arrangement
makes permissions-checking needlessly expensive when many inheritance
children are added to the range range, because while their permissions
need not be checked, the only way to find that out is by seeing
requiredPerms == 0 in the their RTEs.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RelPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the entry's index in the query's list of
RelPermissionInfos.
---
 contrib/postgres_fdw/postgres_fdw.c         |  81 +++--
 contrib/sepgsql/dml.c                       |  42 +--
 contrib/sepgsql/hooks.c                     |   6 +-
 src/backend/access/common/attmap.c          |  13 +-
 src/backend/access/common/tupconvert.c      |   2 +-
 src/backend/catalog/partition.c             |   3 +-
 src/backend/commands/copy.c                 |  18 +-
 src/backend/commands/copyfrom.c             |   9 +
 src/backend/commands/indexcmds.c            |   3 +-
 src/backend/commands/tablecmds.c            |  24 +-
 src/backend/commands/view.c                 |   6 +-
 src/backend/executor/execMain.c             | 105 +++---
 src/backend/executor/execParallel.c         |   1 +
 src/backend/executor/execPartition.c        |  15 +-
 src/backend/executor/execUtils.c            | 159 ++++++---
 src/backend/nodes/copyfuncs.c               |  32 +-
 src/backend/nodes/equalfuncs.c              |  17 +-
 src/backend/nodes/outfuncs.c                |  29 +-
 src/backend/nodes/readfuncs.c               |  21 +-
 src/backend/optimizer/plan/createplan.c     |   6 +-
 src/backend/optimizer/plan/planner.c        |   6 +
 src/backend/optimizer/plan/setrefs.c        | 128 +++----
 src/backend/optimizer/plan/subselect.c      |   6 +
 src/backend/optimizer/prep/prepjointree.c   |  20 +-
 src/backend/optimizer/util/inherit.c        | 170 +++++++---
 src/backend/optimizer/util/relnode.c        |  21 +-
 src/backend/parser/analyze.c                |  60 +++-
 src/backend/parser/parse_clause.c           |   9 +-
 src/backend/parser/parse_merge.c            |   9 +-
 src/backend/parser/parse_relation.c         | 357 +++++++++++++++-----
 src/backend/parser/parse_target.c           |  19 +-
 src/backend/parser/parse_utilcmd.c          |   9 +-
 src/backend/replication/logical/worker.c    |  13 +-
 src/backend/replication/pgoutput/pgoutput.c |   2 +-
 src/backend/rewrite/rewriteDefine.c         |  25 +-
 src/backend/rewrite/rewriteHandler.c        | 189 +++++------
 src/backend/rewrite/rowsecurity.c           |  24 +-
 src/backend/statistics/extended_stats.c     |   7 +-
 src/backend/utils/adt/ri_triggers.c         |  34 +-
 src/backend/utils/adt/selfuncs.c            |  13 +-
 src/backend/utils/cache/relcache.c          |   4 +-
 src/include/access/attmap.h                 |   6 +-
 src/include/commands/copyfrom_internal.h    |   3 +-
 src/include/executor/executor.h             |   7 +-
 src/include/nodes/execnodes.h               |   9 +
 src/include/nodes/nodes.h                   |   1 +
 src/include/nodes/parsenodes.h              |  89 +++--
 src/include/nodes/pathnodes.h               |   2 +
 src/include/nodes/plannodes.h               |   4 +
 src/include/optimizer/inherit.h             |   1 +
 src/include/parser/parse_node.h             |   6 +-
 src/include/parser/parse_relation.h         |   8 +
 src/include/rewrite/rewriteHandler.h        |   2 +-
 53 files changed, 1173 insertions(+), 682 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 56654844e8..435afe5a62 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -30,6 +30,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -38,6 +39,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -458,7 +460,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -623,7 +626,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -657,12 +659,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1515,16 +1517,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1816,7 +1817,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1895,6 +1897,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1906,6 +1937,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1913,6 +1945,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1936,6 +1969,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1947,7 +1981,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2139,6 +2174,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2216,6 +2252,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2232,7 +2270,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2618,7 +2657,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2638,13 +2676,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3942,12 +3979,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3959,12 +3996,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..2cf75b6a6d 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..cb9aeb175d 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..7bc85d9eb5 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a0c897cc9..ff6213f371 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index db6eb6fae7..9e42904c6b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1383,7 +1389,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba..356a93f7c5 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1231,7 +1231,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 51b4a00d50..8cbb1cb830 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1201,7 +1201,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9626,7 +9627,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9793,7 +9795,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9999,7 +10002,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10177,7 +10181,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12271,7 +12276,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18046,7 +18052,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18973,7 +18980,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c6..f0958f03a0 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,7 +353,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -397,10 +397,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..836e5ef344 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,7 +74,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -567,38 +567,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -606,32 +607,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 9a0d5d59ef..815577e5a2 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index aca42ca5b8..8155c7de59 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -576,7 +576,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -633,7 +634,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -775,7 +777,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -873,7 +876,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1135,7 +1139,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..e9cf39dafb 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c42d2ed814..9ec27e0bd7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -97,6 +97,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -782,6 +783,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1272,6 +1274,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2802,6 +2823,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(perminfoindex);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
@@ -2827,12 +2849,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3551,6 +3567,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5563,6 +5580,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f6dd61370c..0103e08c36 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1245,6 +1245,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -3043,6 +3044,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(perminfoindex);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
@@ -3068,17 +3070,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -4187,6 +4197,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6e39590730..afd0795bfb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -315,6 +315,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -711,6 +712,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -999,6 +1001,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2390,6 +2407,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3195,6 +3213,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3402,6 +3421,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3455,12 +3475,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -4153,6 +4167,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c94b2561f0..5f613235a3 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -264,6 +264,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1637,6 +1638,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -1700,13 +1702,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_OID_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
 	READ_OID_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1785,6 +1798,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2272,6 +2286,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -3050,6 +3065,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RANGETBLENTRY", 13))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 179c87c671..e9efb3edef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4095,6 +4095,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5745,7 +5748,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 547fda20a2..bcab1f567c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5988,6 +5992,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6115,6 +6120,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index bf4c722c02..59d84a7613 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -104,9 +105,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -259,7 +258,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -345,10 +344,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down any subquery
+ * RTEs of the current plan level that did not make into the plan tree by way
+ * of being pulled up, nor turned into SubqueryScan nodes referenced in the
+ * plan tree.  Such subqueries would not thus have had any RelPermissionInfos
+ * referenced in them merged into root->parse->relpermlist, so we must force-
+ * add them to glob->finalrelpermlist.  Failing to do so would prevent the
+ * executor from performing expected permission checks for tables mentioned in
+ * such subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -356,34 +362,14 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 
 	/*
 	 * Add the query's own RTEs to the flattened rangetable.
-	 *
-	 * At top level, we must add all RTEs so that their indexes in the
-	 * flattened rangetable match up with their original indexes.  When
-	 * recursing, we only care about extracting relation RTEs.
-	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
 	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -400,73 +386,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					MergeRelPermissionInfos(&glob->finalrelpermlist,
+											rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
-
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
 
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
+	/* Now merge the main query's RelPermissionInfos into the global list. */
+	MergeRelPermissionInfos(&glob->finalrelpermlist, root->parse->relpermlist);
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally update the flat rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(glob->finalrtable,
+									  glob->finalrelpermlist);
 }
 
 /*
@@ -475,9 +417,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
@@ -488,6 +428,14 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
 	memcpy(newrte, rte, sizeof(RangeTblEntry));
 
+	/*
+	 * Executor may need to look up RelPermissionInfos, so must keep
+	 * perminfoindex around.  Though, the list containing the RelPermissionInfo
+	 * to which the index points will be folded into glob->finalrelpermlist, so
+	 * offset the index likewise.
+	 */
+	newrte->perminfoindex += list_length(glob->finalrelpermlist);
+
 	/* zap unneeded sub-structure */
 	newrte->tablesample = NULL;
 	newrte->subquery = NULL;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 863e0e24a1..7b55f16d76 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1507,6 +1507,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subselect->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..ae36b47e58 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1211,6 +1204,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1349,6 +1348,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/* Add the child query's RelPermissionInfos into the parent query. */
+	MergeRelPermissionInfos(&root->parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(root->parse->rtable,
+									  root->parse->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f3..919e2ec1e9 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..64a00b541b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RelPermissionInfo, though
+		 * only the tables mentioned in query are assigned RelPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RelPermissionInfo *perminfo =
+				GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0144284aa3..dd386ac4ae 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -550,7 +551,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -669,6 +670,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +918,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +946,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1105,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1398,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1625,6 +1633,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1871,6 +1880,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2342,6 +2352,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2408,6 +2419,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2426,7 +2438,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2438,7 +2450,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2479,8 +2491,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2767,6 +2779,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3245,9 +3258,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3303,9 +3323,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index d8b14ba7cd..7c10f262e0 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3241,16 +3241,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 5d0035a12b..281ac6cbb4 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -209,6 +209,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 										exprLocation(stmt->sourceRelation));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -281,7 +282,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RelPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -340,7 +341,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -354,8 +355,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7efa5f15d7..3ab6c8ef67 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,12 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static RelPermissionInfo *AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex);
+static RelPermissionInfo *GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok);
+static Index GetRelPermissionInfoIndex(List *relpermlist, Oid relid);
 
 
 /*
@@ -1021,10 +1027,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1244,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1286,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1430,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1467,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1476,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1497,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1558,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1567,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1588,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1639,21 +1658,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1946,20 +1959,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1971,7 +1977,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2017,20 +2023,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2105,19 +2104,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2196,19 +2189,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2223,6 +2210,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2346,19 +2334,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2472,16 +2454,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2493,7 +2472,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3114,6 +3093,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3131,7 +3111,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3680,3 +3663,221 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless one with the same OID is found in it
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	Assert(rte->rtekind == RTE_RELATION);
+
+	Assert(rte->perminfoindex == 0);
+	return AddRelPermissionInfoInternal(relpermlist, rte->relid,
+										&rte->perminfoindex);
+}
+
+/*
+ * AddRelPermissionInfoInternal
+ *		Sub-routine of AddRelPermissionInfo that does the actual work
+ */
+static RelPermissionInfo *
+AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex)
+{
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfoInternal(*relpermlist, relid, perminfoindex,
+											true);
+	if (perminfo)
+	{
+		Assert(*perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Note its index.  */
+	*perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Find RelPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(relpermlist))
+		elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+			 rte->relid);
+	perminfo = GetRelPermissionInfoInternal(relpermlist, rte->relid,
+											&rte->perminfoindex, false);
+	if (rte->relid != perminfo->relid)
+		elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfoInternal
+ *		Sub-routine of GetRelPermissionInfo that does the actual work
+ *
+ * If *perminfoindex is 0, the list is scanned to find one with given relid.
+ * If found, *perminfoindex is set to its 1-based index in the list.
+ *
+ * If *perminfoindex is already valid (> 0), it means that the caller expects
+ * to find the entry it's looking for at that location in the list.
+ */
+static RelPermissionInfo *
+GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	if (*perminfoindex == 0)
+	{
+		ListCell   *lc;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == relid)
+			{
+				*perminfoindex = foreach_current_index(lc) + 1;
+				return perminfo;
+			}
+		}
+	}
+	else if (*perminfoindex > 0)
+	{
+
+		perminfo = (RelPermissionInfo *)
+			list_nth(relpermlist, *perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * GetRelPermissionInfoIndex
+ *		Returns a 1-based index of the RelPermissionInfo of matching relid if
+ *		found in the given list
+ *
+ * 0 indicates that one was not found.
+ */
+static Index
+GetRelPermissionInfoIndex(List *relpermlist, Oid relid)
+{
+	ListCell   *lc;
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return foreach_current_index(lc) + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source query (src_relpermlist)
+ *		into the destination query's list (*dest_relpermlist), "merging"
+ *		properties of any that are present in both.
+ *
+ * Caller must subsequently call ReassignRangeTablePermInfoIndexes() on the
+ * source query's range table.
+ */
+void
+MergeRelPermissionInfos(List **dest_relpermlist, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *dest_perminfo;
+		Index		ignored = 0;
+
+		dest_perminfo = AddRelPermissionInfoInternal(dest_relpermlist,
+													 src_perminfo->relid,
+													 &ignored);
+
+		dest_perminfo->inh |= src_perminfo->inh;
+		dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+		if (!OidIsValid(dest_perminfo->checkAsUser))
+			dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+		dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+												src_perminfo->selectedCols);
+		dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+												src_perminfo->insertedCols);
+		dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+											   src_perminfo->updatedCols);
+		dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+													src_perminfo->extraUpdatedCols);
+	}
+}
+
+/*
+ * ReassignRangeTablePermInfoIndexes
+ * 		Updates perminfoindex of the relation RTEs in rtable so that they reflect
+ * 		their respective RelPermissionInfo entry's current position in permlist.
+ *
+ * This is provided for the sites that use MergeRelPermissionInfos() to merge
+ * the RelPermissionInfo entries of two queries.
+ */
+void
+ReassignRangeTablePermInfoIndexes(List *rtable, List *relpermlist)
+{
+	ListCell   *l;
+
+	foreach(l, rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		/*
+		 * Only RELATIONs would have been assigned a RelPermissionInfo and that
+		 * too only those that are mentioned in the query (not inheritance
+		 * child relations that are added afterwards), so we also check that
+		 * the RTE's existing index is valid.
+		 */
+		if (rte->rtekind != RTE_RELATION && rte->perminfoindex > 0)
+			continue;
+		rte->perminfoindex = GetRelPermissionInfoIndex(relpermlist, rte->relid);
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 62d5d7d439..0b7f1c80b3 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cd946c7692..e6117823dd 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1221,7 +1221,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3013,9 +3014,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3071,6 +3069,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3113,8 +3112,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index f3868b3e1f..6745972d76 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -157,6 +157,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1830,7 +1833,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1878,7 +1881,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1888,14 +1891,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 893833ea83..ff58ea63ca 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1053,7 +1053,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..9144843fd5 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -787,14 +788,7 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ *		field to the given userid in all RelPermissionInfos of the query.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -821,18 +815,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RelPermissionInfos for this query. */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..fca2a1eaf0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,27 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(&sub_action->relpermlist, parsetree->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(sub_action->rtable,
+									  sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1594,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1623,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3174,6 +3150,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..d0a292d46c 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ca48395d5c..cc68e68f1d 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1560,6 +1561,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1607,10 +1609,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 01d4c22cfc..4590e1148c 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1367,8 +1367,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1386,32 +1386,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1421,9 +1415,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1fbb0b28c3..faa2086215 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5137,7 +5137,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5189,7 +5189,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5268,7 +5268,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5316,7 +5316,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5377,6 +5377,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5385,7 +5386,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5454,7 +5455,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a15ce9edb1..982c4ce33c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -846,8 +846,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RelPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 3df1c5a97c..af40f21496 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 873772f188..222acf6e88 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -602,6 +603,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cbbcff81d2..7b04008a12 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -548,6 +548,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -595,6 +603,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d48147abee..73a0bf8cf3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -91,6 +91,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0ff4ba0884..1961964395 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -147,6 +147,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -953,37 +955,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1037,11 +1008,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1161,14 +1138,60 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table, though
+ * 		different RTEs for the same relation share the RelPermissionInfo, that
+ * 		is, there is only one RelPermissionInfo containing a given relid.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 365000bdcd..7b03f8321a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat" list of RelPermissionInfo */
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 50ef3dda05..66fb82ab01 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -66,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -654,6 +657,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index cf9c759025..e573b1620f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -181,6 +181,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +236,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -268,6 +271,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index de21c3c649..923add1176 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,13 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
+											   RangeTblEntry *rte);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
+											   RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(List **dest_relpermlist,
+									List *src_relpermlist);
+extern void ReassignRangeTablePermInfoIndexes(List *rtable,
+											  List *relpermlist);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..f21786da35 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-04-11 05:41  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-04-11 05:41 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

Rebased to keep the cfbot green for now.

-- 
Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v13-0001-Rework-query-relation-permission-checking.patch (158.6K, ../../CA+HiwqHFtydB5O_sZe3shWDUSM=K0NJ6HWRv4pdTuNk5zeC_ZQ@mail.gmail.com/2-v13-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 13fb776cf80fadbee8d8209749327b39e6c3c4c3 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v13 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table to look for any
RTE_RELATION entries to have permissions checked.  This arrangement
makes permissions-checking needlessly expensive when many inheritance
children are added to the range range, because while their permissions
need not be checked, the only way to find that out is by seeing
requiredPerms == 0 in the their RTEs.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RelPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the entry's index in the query's list of
RelPermissionInfos.
---
 contrib/postgres_fdw/postgres_fdw.c         |  81 +++--
 contrib/sepgsql/dml.c                       |  42 +--
 contrib/sepgsql/hooks.c                     |   6 +-
 src/backend/access/common/attmap.c          |  13 +-
 src/backend/access/common/tupconvert.c      |   2 +-
 src/backend/catalog/partition.c             |   3 +-
 src/backend/commands/copy.c                 |  18 +-
 src/backend/commands/copyfrom.c             |   9 +
 src/backend/commands/indexcmds.c            |   3 +-
 src/backend/commands/tablecmds.c            |  24 +-
 src/backend/commands/view.c                 |   6 +-
 src/backend/executor/execMain.c             | 105 +++---
 src/backend/executor/execParallel.c         |   1 +
 src/backend/executor/execPartition.c        |  15 +-
 src/backend/executor/execUtils.c            | 159 ++++++---
 src/backend/nodes/copyfuncs.c               |  32 +-
 src/backend/nodes/equalfuncs.c              |  17 +-
 src/backend/nodes/outfuncs.c                |  29 +-
 src/backend/nodes/readfuncs.c               |  21 +-
 src/backend/optimizer/plan/createplan.c     |   6 +-
 src/backend/optimizer/plan/planner.c        |   6 +
 src/backend/optimizer/plan/setrefs.c        | 128 +++----
 src/backend/optimizer/plan/subselect.c      |   6 +
 src/backend/optimizer/prep/prepjointree.c   |  20 +-
 src/backend/optimizer/util/inherit.c        | 170 +++++++---
 src/backend/optimizer/util/relnode.c        |  21 +-
 src/backend/parser/analyze.c                |  60 +++-
 src/backend/parser/parse_clause.c           |   9 +-
 src/backend/parser/parse_merge.c            |   9 +-
 src/backend/parser/parse_relation.c         | 357 +++++++++++++++-----
 src/backend/parser/parse_target.c           |  19 +-
 src/backend/parser/parse_utilcmd.c          |   9 +-
 src/backend/replication/logical/worker.c    |  13 +-
 src/backend/replication/pgoutput/pgoutput.c |   2 +-
 src/backend/rewrite/rewriteDefine.c         |  25 +-
 src/backend/rewrite/rewriteHandler.c        | 189 +++++------
 src/backend/rewrite/rowsecurity.c           |  24 +-
 src/backend/statistics/extended_stats.c     |   7 +-
 src/backend/utils/adt/ri_triggers.c         |  34 +-
 src/backend/utils/adt/selfuncs.c            |  13 +-
 src/backend/utils/cache/relcache.c          |   4 +-
 src/include/access/attmap.h                 |   6 +-
 src/include/commands/copyfrom_internal.h    |   3 +-
 src/include/executor/executor.h             |   7 +-
 src/include/nodes/execnodes.h               |   9 +
 src/include/nodes/nodes.h                   |   1 +
 src/include/nodes/parsenodes.h              |  89 +++--
 src/include/nodes/pathnodes.h               |   2 +
 src/include/nodes/plannodes.h               |   4 +
 src/include/optimizer/inherit.h             |   1 +
 src/include/parser/parse_node.h             |   6 +-
 src/include/parser/parse_relation.h         |   8 +
 src/include/rewrite/rewriteHandler.h        |   2 +-
 53 files changed, 1173 insertions(+), 682 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index c51dd68722..77970b5d0f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1934,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1942,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1966,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1978,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2136,6 +2171,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2213,6 +2249,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2229,7 +2267,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2615,7 +2654,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2635,13 +2673,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3939,12 +3976,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3956,12 +3993,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..2cf75b6a6d 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..cb9aeb175d 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..7bc85d9eb5 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 689713ea58..761653f6d5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 35a1d3a774..c8c9e87ef9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1383,7 +1389,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba..356a93f7c5 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1231,7 +1231,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 90edd0bb97..12b8c3e52a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1201,7 +1201,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9651,7 +9652,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9818,7 +9820,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10024,7 +10027,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10202,7 +10206,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12296,7 +12301,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18048,7 +18054,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18975,7 +18982,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c6..f0958f03a0 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,7 +353,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -397,10 +397,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..836e5ef344 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,7 +74,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -567,38 +567,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -606,32 +607,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 9a0d5d59ef..815577e5a2 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 615bd80973..db91aaa615 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -781,7 +783,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -879,7 +882,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1141,7 +1145,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..e9cf39dafb 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427ea8..bdf8cb2aa8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -97,6 +97,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -783,6 +784,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1276,6 +1278,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2948,6 +2969,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(perminfoindex);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
@@ -2973,12 +2995,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3698,6 +3714,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5710,6 +5727,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e013c1bbfe..8863f044ae 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1332,6 +1332,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -3130,6 +3131,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(perminfoindex);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
@@ -3155,17 +3157,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -4290,6 +4300,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d5f5e76c55..38183616e6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -315,6 +315,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -712,6 +713,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -1003,6 +1005,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2422,6 +2439,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3227,6 +3245,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3435,6 +3454,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3488,12 +3508,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -4186,6 +4200,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3d150cb25d..7d05146d38 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -264,6 +264,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1668,6 +1669,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -1731,13 +1733,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_OID_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
 	READ_OID_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1816,6 +1829,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2304,6 +2318,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -3085,6 +3100,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RANGETBLENTRY", 13))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 95476ada0b..e017dd34fc 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4129,6 +4129,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5780,7 +5783,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b090b087e9..b70778d9fa 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6001,6 +6005,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6128,6 +6133,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6ea3505646..70da36e390 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -111,9 +112,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -268,7 +267,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -354,10 +353,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down any subquery
+ * RTEs of the current plan level that did not make into the plan tree by way
+ * of being pulled up, nor turned into SubqueryScan nodes referenced in the
+ * plan tree.  Such subqueries would not thus have had any RelPermissionInfos
+ * referenced in them merged into root->parse->relpermlist, so we must force-
+ * add them to glob->finalrelpermlist.  Failing to do so would prevent the
+ * executor from performing expected permission checks for tables mentioned in
+ * such subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -365,34 +371,14 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 
 	/*
 	 * Add the query's own RTEs to the flattened rangetable.
-	 *
-	 * At top level, we must add all RTEs so that their indexes in the
-	 * flattened rangetable match up with their original indexes.  When
-	 * recursing, we only care about extracting relation RTEs.
-	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
 	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -409,73 +395,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					MergeRelPermissionInfos(&glob->finalrelpermlist,
+											rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
-
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
 
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
+	/* Now merge the main query's RelPermissionInfos into the global list. */
+	MergeRelPermissionInfos(&glob->finalrelpermlist, root->parse->relpermlist);
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally update the flat rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(glob->finalrtable,
+									  glob->finalrelpermlist);
 }
 
 /*
@@ -484,9 +426,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
@@ -497,6 +437,14 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
 	memcpy(newrte, rte, sizeof(RangeTblEntry));
 
+	/*
+	 * Executor may need to look up RelPermissionInfos, so must keep
+	 * perminfoindex around.  Though, the list containing the RelPermissionInfo
+	 * to which the index points will be folded into glob->finalrelpermlist, so
+	 * offset the index likewise.
+	 */
+	newrte->perminfoindex += list_length(glob->finalrelpermlist);
+
 	/* zap unneeded sub-structure */
 	newrte->tablesample = NULL;
 	newrte->subquery = NULL;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 863e0e24a1..7b55f16d76 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1507,6 +1507,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subselect->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..ae36b47e58 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1211,6 +1204,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1349,6 +1348,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/* Add the child query's RelPermissionInfos into the parent query. */
+	MergeRelPermissionInfos(&root->parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(root->parse->rtable,
+									  root->parse->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f3..919e2ec1e9 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..64a00b541b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RelPermissionInfo, though
+		 * only the tables mentioned in query are assigned RelPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RelPermissionInfo *perminfo =
+				GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0144284aa3..dd386ac4ae 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -550,7 +551,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -669,6 +670,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +918,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +946,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1105,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1398,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1625,6 +1633,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1871,6 +1880,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2342,6 +2352,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2408,6 +2419,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2426,7 +2438,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2438,7 +2450,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2479,8 +2491,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2767,6 +2779,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3245,9 +3258,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3303,9 +3323,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index dafde68b20..ee5dba0960 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3247,16 +3247,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 5d0035a12b..281ac6cbb4 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -209,6 +209,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 										exprLocation(stmt->sourceRelation));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -281,7 +282,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RelPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -340,7 +341,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -354,8 +355,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 5448cb01fa..226ba134c5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,12 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static RelPermissionInfo *AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex);
+static RelPermissionInfo *GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok);
+static Index GetRelPermissionInfoIndex(List *relpermlist, Oid relid);
 
 
 /*
@@ -1021,10 +1027,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1244,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1286,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1430,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1467,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1476,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1497,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1558,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1567,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1588,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1639,21 +1658,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1946,20 +1959,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1971,7 +1977,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2018,20 +2024,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2106,19 +2105,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2197,19 +2190,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2224,6 +2211,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2347,19 +2335,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2473,16 +2455,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2494,7 +2473,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3115,6 +3094,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3132,7 +3112,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3681,3 +3664,221 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless one with the same OID is found in it
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	Assert(rte->rtekind == RTE_RELATION);
+
+	Assert(rte->perminfoindex == 0);
+	return AddRelPermissionInfoInternal(relpermlist, rte->relid,
+										&rte->perminfoindex);
+}
+
+/*
+ * AddRelPermissionInfoInternal
+ *		Sub-routine of AddRelPermissionInfo that does the actual work
+ */
+static RelPermissionInfo *
+AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex)
+{
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfoInternal(*relpermlist, relid, perminfoindex,
+											true);
+	if (perminfo)
+	{
+		Assert(*perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Note its index.  */
+	*perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Find RelPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(relpermlist))
+		elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+			 rte->relid);
+	perminfo = GetRelPermissionInfoInternal(relpermlist, rte->relid,
+											&rte->perminfoindex, false);
+	if (rte->relid != perminfo->relid)
+		elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfoInternal
+ *		Sub-routine of GetRelPermissionInfo that does the actual work
+ *
+ * If *perminfoindex is 0, the list is scanned to find one with given relid.
+ * If found, *perminfoindex is set to its 1-based index in the list.
+ *
+ * If *perminfoindex is already valid (> 0), it means that the caller expects
+ * to find the entry it's looking for at that location in the list.
+ */
+static RelPermissionInfo *
+GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	if (*perminfoindex == 0)
+	{
+		ListCell   *lc;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == relid)
+			{
+				*perminfoindex = foreach_current_index(lc) + 1;
+				return perminfo;
+			}
+		}
+	}
+	else if (*perminfoindex > 0)
+	{
+
+		perminfo = (RelPermissionInfo *)
+			list_nth(relpermlist, *perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * GetRelPermissionInfoIndex
+ *		Returns a 1-based index of the RelPermissionInfo of matching relid if
+ *		found in the given list
+ *
+ * 0 indicates that one was not found.
+ */
+static Index
+GetRelPermissionInfoIndex(List *relpermlist, Oid relid)
+{
+	ListCell   *lc;
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return foreach_current_index(lc) + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source query (src_relpermlist)
+ *		into the destination query's list (*dest_relpermlist), "merging"
+ *		properties of any that are present in both.
+ *
+ * Caller must subsequently call ReassignRangeTablePermInfoIndexes() on the
+ * source query's range table.
+ */
+void
+MergeRelPermissionInfos(List **dest_relpermlist, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *dest_perminfo;
+		Index		ignored = 0;
+
+		dest_perminfo = AddRelPermissionInfoInternal(dest_relpermlist,
+													 src_perminfo->relid,
+													 &ignored);
+
+		dest_perminfo->inh |= src_perminfo->inh;
+		dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+		if (!OidIsValid(dest_perminfo->checkAsUser))
+			dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+		dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+												src_perminfo->selectedCols);
+		dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+												src_perminfo->insertedCols);
+		dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+											   src_perminfo->updatedCols);
+		dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+													src_perminfo->extraUpdatedCols);
+	}
+}
+
+/*
+ * ReassignRangeTablePermInfoIndexes
+ * 		Updates perminfoindex of the relation RTEs in rtable so that they reflect
+ * 		their respective RelPermissionInfo entry's current position in permlist.
+ *
+ * This is provided for the sites that use MergeRelPermissionInfos() to merge
+ * the RelPermissionInfo entries of two queries.
+ */
+void
+ReassignRangeTablePermInfoIndexes(List *rtable, List *relpermlist)
+{
+	ListCell   *l;
+
+	foreach(l, rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		/*
+		 * Only RELATIONs would have been assigned a RelPermissionInfo and that
+		 * too only those that are mentioned in the query (not inheritance
+		 * child relations that are added afterwards), so we also check that
+		 * the RTE's existing index is valid.
+		 */
+		if (rte->rtekind != RTE_RELATION && rte->perminfoindex > 0)
+			continue;
+		rte->perminfoindex = GetRelPermissionInfoIndex(relpermlist, rte->relid);
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..4543949a54 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2826559d09..5ca08dc6a1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1222,7 +1222,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3014,9 +3015,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3072,6 +3070,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3114,8 +3113,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 9181d3e863..6c327f3573 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -491,6 +492,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1778,7 +1781,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1826,7 +1829,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1836,14 +1839,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index fe5accca57..5b16b78bb2 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1108,7 +1108,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..9144843fd5 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -787,14 +788,7 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ *		field to the given userid in all RelPermissionInfos of the query.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -821,18 +815,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RelPermissionInfos for this query. */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..fca2a1eaf0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,27 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(&sub_action->relpermlist, parsetree->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(sub_action->rtable,
+									  sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1594,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1623,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3174,6 +3150,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..d0a292d46c 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ca48395d5c..cc68e68f1d 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1560,6 +1561,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1607,10 +1609,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 01d4c22cfc..4590e1148c 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1367,8 +1367,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1386,32 +1386,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1421,9 +1415,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fb4fb987e7..49b1e31bc1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5169,7 +5169,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5221,7 +5221,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5300,7 +5300,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5348,7 +5348,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5409,6 +5409,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5417,7 +5418,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5486,7 +5487,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 43f14c233d..5610ada989 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -846,8 +846,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RelPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 3df1c5a97c..af40f21496 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 873772f188..222acf6e88 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -602,6 +603,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 94b191f8ae..4dc29324cd 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -548,6 +548,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -595,6 +603,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 340d28f4e1..282c9ebb28 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -91,6 +91,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da02658c81..cf8a28ed8f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -147,6 +147,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -953,37 +955,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1037,11 +1008,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1161,14 +1138,60 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table, though
+ * 		different RTEs for the same relation share the RelPermissionInfo, that
+ * 		is, there is only one RelPermissionInfo containing a given relid.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c5ab53e05c..eec7975afc 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat" list of RelPermissionInfo */
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e43e360d9b..5832905e22 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -66,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -666,6 +669,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index cf9c759025..e573b1620f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -181,6 +181,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +236,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -268,6 +271,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index de21c3c649..923add1176 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,13 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
+											   RangeTblEntry *rte);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
+											   RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(List **dest_relpermlist,
+									List *src_relpermlist);
+extern void ReassignRangeTablePermInfoIndexes(List *rtable,
+											  List *relpermlist);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..f21786da35 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.24.1



  [application/octet-stream] v13-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (122.1K, ../../CA+HiwqHFtydB5O_sZe3shWDUSM=K0NJ6HWRv4pdTuNk5zeC_ZQ@mail.gmail.com/3-v13-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From b1c97ab0ab1b74f11a96e7705e1e0316844e27a8 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v13 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add an RTE for view relations.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  34 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/jsonb_sqljson.out   |  62 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 726 +++++++++---------
 src/test/regress/expected/sqljson.out         |   6 +-
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 28 files changed, 757 insertions(+), 836 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 30e95f585f..472cd776e2 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2485,7 +2485,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2548,7 +2548,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6423,10 +6423,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6517,10 +6517,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b97b8b0435..2cbc53b42c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f0958f03a0..2519970914 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -339,78 +339,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -573,12 +501,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fca2a1eaf0..6225ccba30 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1859,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c65c92bfb0..6fe8e61ab2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2216,7 +2216,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2232,7 +2232,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2248,7 +2248,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2264,7 +2264,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2282,7 +2282,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3237,7 +3237,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 601047fa3d..36bed0ac1b 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1544,7 +1544,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1596,7 +1596,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1612,7 +1612,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1628,7 +1628,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2113,15 +2113,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5ede56d9b5..7315ddb92a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2475,8 +2475,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2487,8 +2487,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2514,8 +2514,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2527,8 +2527,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 32385bbb0e..e60bdf2dff 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1604,9 +1604,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1627,9 +1627,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1719,8 +1719,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1965,7 +1965,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2017,19 +2017,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index 6a56f0b09c..d3e7b23332 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -506,16 +506,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index 28338b4d19..0211e9d282 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -1159,37 +1159,37 @@ SELECT * FROM
 	);
 \sv jsonb_table_view
 CREATE OR REPLACE VIEW public.jsonb_table_view AS
- SELECT "json_table".id,
-    "json_table".id2,
-    "json_table"."int",
-    "json_table".text,
-    "json_table"."char(4)",
-    "json_table".bool,
-    "json_table"."numeric",
-    "json_table".domain,
-    "json_table".js,
-    "json_table".jb,
-    "json_table".jst,
-    "json_table".jsc,
-    "json_table".jsv,
-    "json_table".jsb,
-    "json_table".jsbq,
-    "json_table".aaa,
-    "json_table".aaa1,
-    "json_table".exists1,
-    "json_table".exists2,
-    "json_table".exists3,
-    "json_table".js2,
-    "json_table".jsb2w,
-    "json_table".jsb2q,
-    "json_table".ia,
-    "json_table".ta,
-    "json_table".jba,
-    "json_table".a1,
-    "json_table".b1,
-    "json_table".a11,
-    "json_table".a21,
-    "json_table".a22
+ SELECT id,
+    id2,
+    "int",
+    text,
+    "char(4)",
+    bool,
+    "numeric",
+    domain,
+    js,
+    jb,
+    jst,
+    jsc,
+    jsv,
+    jsb,
+    jsbq,
+    aaa,
+    aaa1,
+    exists1,
+    exists2,
+    exists3,
+    js2,
+    jsb2w,
+    jsb2q,
+    ia,
+    ta,
+    jba,
+    a1,
+    b1,
+    a11,
+    a21,
+    a22
    FROM JSON_TABLE(
             'null'::jsonb, '$[*]' AS json_table_path_1
             PASSING
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index 313c72a268..03d2de7d3a 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 2334a1321e..2c4da34687 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 21effe8315..30bb11ba3a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,13 +1419,13 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1443,10 +1443,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1692,23 +1692,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1722,10 +1722,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1793,13 +1793,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1812,57 +1812,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1885,8 +1885,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1895,13 +1895,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2011,16 +2011,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2058,26 +2058,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2096,41 +2096,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2140,68 +2140,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2218,19 +2218,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2240,19 +2240,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2296,64 +2296,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2538,24 +2538,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3075,7 +3075,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3114,8 +3114,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3127,8 +3127,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3140,8 +3140,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3153,8 +3153,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 6cadd87868..44b84a7188 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1007,7 +1007,7 @@ SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING t
 FROM generate_series(1,5) i;
 \sv json_objectagg_view
 CREATE OR REPLACE VIEW public.json_objectagg_view AS
- SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg"
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_objectagg_view;
 -- Test JSON_ARRAYAGG deparsing
@@ -1043,7 +1043,7 @@ SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text
 FROM generate_series(1,5) i;
 \sv json_arrayagg_view
 CREATE OR REPLACE VIEW public.json_arrayagg_view AS
- SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg"
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_arrayagg_view;
 -- Test JSON_ARRAY(subquery) deparsing
@@ -1261,7 +1261,7 @@ SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT
 \sv is_json_view
 CREATE OR REPLACE VIEW public.is_json_view AS
  SELECT '1'::text IS JSON AS "any",
-    ('1'::text || i.i) IS JSON SCALAR AS scalar,
+    ('1'::text || i) IS JSON SCALAR AS scalar,
     NOT '[]'::text IS JSON ARRAY AS "array",
     '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
    FROM generate_series(1, 3) i(i)
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index cd812336f2..b09603dc98 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index d78b4c463c..b291f36ecf 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7c6de7cc07..bea54c91cd 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -872,9 +872,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1394,9 +1394,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1416,9 +1416,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 55b65ef324..bb994a05de 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 0484260281..974b14f859 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.24.1



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-07-06 03:25  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-07-06 03:25 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Apr 11, 2022 at 2:41 PM Amit Langote <[email protected]> wrote:
> Rebased to keep the cfbot green for now.

And again to fix the rules.out conflicts.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v14-0001-Rework-query-relation-permission-checking.patch (158.6K, ../../CA+HiwqFOq=J=1f_8ecMY146pyups4W05MfrQx_3TVK_emzK7bA@mail.gmail.com/2-v14-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 2dda5b1cf12f8803abd77f86dc43852dc7e39fa1 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v14 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table to look for any
RTE_RELATION entries to have permissions checked.  This arrangement
makes permissions-checking needlessly expensive when many inheritance
children are added to the range range, because while their permissions
need not be checked, the only way to find that out is by seeing
requiredPerms == 0 in the their RTEs.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RelPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the entry's index in the query's list of
RelPermissionInfos.
---
 contrib/postgres_fdw/postgres_fdw.c         |  81 +++--
 contrib/sepgsql/dml.c                       |  42 +--
 contrib/sepgsql/hooks.c                     |   6 +-
 src/backend/access/common/attmap.c          |  13 +-
 src/backend/access/common/tupconvert.c      |   2 +-
 src/backend/catalog/partition.c             |   3 +-
 src/backend/commands/copy.c                 |  18 +-
 src/backend/commands/copyfrom.c             |   9 +
 src/backend/commands/indexcmds.c            |   3 +-
 src/backend/commands/tablecmds.c            |  24 +-
 src/backend/commands/view.c                 |   6 +-
 src/backend/executor/execMain.c             | 105 +++---
 src/backend/executor/execParallel.c         |   1 +
 src/backend/executor/execPartition.c        |  15 +-
 src/backend/executor/execUtils.c            | 159 ++++++---
 src/backend/nodes/copyfuncs.c               |  32 +-
 src/backend/nodes/equalfuncs.c              |  17 +-
 src/backend/nodes/outfuncs.c                |  29 +-
 src/backend/nodes/readfuncs.c               |  21 +-
 src/backend/optimizer/plan/createplan.c     |   6 +-
 src/backend/optimizer/plan/planner.c        |   6 +
 src/backend/optimizer/plan/setrefs.c        | 128 +++----
 src/backend/optimizer/plan/subselect.c      |   6 +
 src/backend/optimizer/prep/prepjointree.c   |  20 +-
 src/backend/optimizer/util/inherit.c        | 170 +++++++---
 src/backend/optimizer/util/relnode.c        |  21 +-
 src/backend/parser/analyze.c                |  60 +++-
 src/backend/parser/parse_clause.c           |   9 +-
 src/backend/parser/parse_merge.c            |   9 +-
 src/backend/parser/parse_relation.c         | 357 +++++++++++++++-----
 src/backend/parser/parse_target.c           |  19 +-
 src/backend/parser/parse_utilcmd.c          |   9 +-
 src/backend/replication/logical/worker.c    |  13 +-
 src/backend/replication/pgoutput/pgoutput.c |   2 +-
 src/backend/rewrite/rewriteDefine.c         |  25 +-
 src/backend/rewrite/rewriteHandler.c        | 189 +++++------
 src/backend/rewrite/rowsecurity.c           |  24 +-
 src/backend/statistics/extended_stats.c     |   7 +-
 src/backend/utils/adt/ri_triggers.c         |  34 +-
 src/backend/utils/adt/selfuncs.c            |  13 +-
 src/backend/utils/cache/relcache.c          |   4 +-
 src/include/access/attmap.h                 |   6 +-
 src/include/commands/copyfrom_internal.h    |   3 +-
 src/include/executor/executor.h             |   7 +-
 src/include/nodes/execnodes.h               |   9 +
 src/include/nodes/nodes.h                   |   1 +
 src/include/nodes/parsenodes.h              |  89 +++--
 src/include/nodes/pathnodes.h               |   2 +
 src/include/nodes/plannodes.h               |   4 +
 src/include/optimizer/inherit.h             |   1 +
 src/include/parser/parse_node.h             |   6 +-
 src/include/parser/parse_relation.h         |   8 +
 src/include/rewrite/rewriteHandler.h        |   2 +-
 53 files changed, 1173 insertions(+), 682 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 955a428e3d..b932d3a5ed 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1934,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1942,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1966,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1978,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2145,6 +2180,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2222,6 +2258,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2238,7 +2276,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2624,7 +2663,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2644,13 +2682,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3945,12 +3982,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3962,12 +3999,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..2cf75b6a6d 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..cb9aeb175d 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..7bc85d9eb5 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e2870e3c11..10c1d3c2b4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 35a1d3a774..c8c9e87ef9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1383,7 +1389,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 99f5ab83c3..bad38f0c95 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1279,7 +1279,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2de0ebacec..53c2978738 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1200,7 +1200,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9650,7 +9651,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9817,7 +9819,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10023,7 +10026,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10201,7 +10205,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12295,7 +12300,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18047,7 +18053,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18974,7 +18981,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c6..f0958f03a0 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,7 +353,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -397,10 +397,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..836e5ef344 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,7 +74,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -567,38 +567,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -606,32 +607,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f1fd7f7e8b..a89ab74a7e 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index e03ea27299..85a1dc84a6 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -781,7 +783,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -879,7 +882,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1141,7 +1145,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..e9cf39dafb 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 706d283a92..5d3b675f9b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -97,6 +97,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -783,6 +784,7 @@ _copyForeignScan(const ForeignScan *from)
 	 */
 	COPY_SCALAR_FIELD(operation);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(checkAsUser);
 	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
@@ -1276,6 +1278,25 @@ _copyPlanRowMark(const PlanRowMark *from)
 
 	return newnode;
 }
+/*
+ * _copyRelPermissionInfo
+ */
+static RelPermissionInfo *
+_copyRelPermissionInfo(const RelPermissionInfo *from)
+{
+	RelPermissionInfo *newnode = makeNode(RelPermissionInfo);
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(inh);
+	COPY_SCALAR_FIELD(requiredPerms);
+	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(insertedCols);
+	COPY_BITMAPSET_FIELD(updatedCols);
+	COPY_BITMAPSET_FIELD(extraUpdatedCols);
+
+	return newnode;
+}
 
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
@@ -2950,6 +2971,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
 	COPY_NODE_FIELD(tablesample);
+	COPY_SCALAR_FIELD(perminfoindex);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
@@ -2975,12 +2997,6 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(lateral);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(requiredPerms);
-	COPY_SCALAR_FIELD(checkAsUser);
-	COPY_BITMAPSET_FIELD(selectedCols);
-	COPY_BITMAPSET_FIELD(insertedCols);
-	COPY_BITMAPSET_FIELD(updatedCols);
-	COPY_BITMAPSET_FIELD(extraUpdatedCols);
 	COPY_NODE_FIELD(securityQuals);
 
 	return newnode;
@@ -3700,6 +3716,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(isReturn);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(relpermlist);
 	COPY_NODE_FIELD(jointree);
 	COPY_NODE_FIELD(targetList);
 	COPY_SCALAR_FIELD(override);
@@ -5712,6 +5729,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_RelPermissionInfo:
+			retval = _copyRelPermissionInfo(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index fccc0b4a18..287dbca5a4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1350,6 +1350,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(isReturn);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
+	COMPARE_NODE_FIELD(relpermlist);
 	COMPARE_NODE_FIELD(jointree);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_SCALAR_FIELD(override);
@@ -3147,6 +3148,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
 	COMPARE_NODE_FIELD(tablesample);
+	COMPARE_SCALAR_FIELD(perminfoindex);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
@@ -3172,17 +3174,25 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
+	COMPARE_NODE_FIELD(securityQuals);
+
+	return true;
+}
+
+static bool
+_equalRelPermissionInfo(const RelPermissionInfo *a, const RelPermissionInfo *b)
+{
+	COMPARE_SCALAR_FIELD(relid);
+	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 	COMPARE_BITMAPSET_FIELD(selectedCols);
 	COMPARE_BITMAPSET_FIELD(insertedCols);
 	COMPARE_BITMAPSET_FIELD(updatedCols);
 	COMPARE_BITMAPSET_FIELD(extraUpdatedCols);
-	COMPARE_NODE_FIELD(securityQuals);
 
 	return true;
 }
-
 static bool
 _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 {
@@ -4307,6 +4317,9 @@ equal(const void *a, const void *b)
 		case T_RangeTblEntry:
 			retval = _equalRangeTblEntry(a, b);
 			break;
+		case T_RelPermissionInfo:
+			retval = _equalRelPermissionInfo(a, b);
+			break;
 		case T_RangeTblFunction:
 			retval = _equalRangeTblFunction(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4315c53080..4cd0741549 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -326,6 +326,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -723,6 +724,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	WRITE_ENUM_FIELD(operation, CmdType);
 	WRITE_UINT_FIELD(resultRelation);
+	WRITE_OID_FIELD(checkAsUser);
 	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
@@ -1014,6 +1016,21 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outRelPermissionInfo(StringInfo str, const RelPermissionInfo *node)
+{
+	WRITE_NODE_TYPE("RELPERMISSIONINFO");
+
+	WRITE_UINT_FIELD(relid);
+	WRITE_BOOL_FIELD(inh);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(insertedCols);
+	WRITE_BITMAPSET_FIELD(updatedCols);
+	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -2435,6 +2452,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_NODE_FIELD(finalrelpermlist);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -3240,6 +3258,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -3448,6 +3467,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3501,12 +3521,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
@@ -4199,6 +4213,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_RelPermissionInfo:
+				_outRelPermissionInfo(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6a05b69415..6ef93cce62 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -264,6 +264,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -1670,6 +1671,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -1733,13 +1735,24 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
+	READ_NODE_FIELD(securityQuals);
+
+	READ_DONE();
+}
+
+static RelPermissionInfo *
+_readRelPermissionInfo(void)
+{
+	READ_LOCALS(RelPermissionInfo);
+
+	READ_OID_FIELD(relid);
+	READ_BOOL_FIELD(inh);
+	READ_INT_FIELD(requiredPerms);
 	READ_OID_FIELD(checkAsUser);
 	READ_BITMAPSET_FIELD(selectedCols);
 	READ_BITMAPSET_FIELD(insertedCols);
 	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
-	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
 }
@@ -1818,6 +1831,7 @@ _readPlannedStmt(void)
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2306,6 +2320,7 @@ _readForeignScan(void)
 
 	READ_ENUM_FIELD(operation, CmdType);
 	READ_UINT_FIELD(resultRelation);
+	READ_OID_FIELD(checkAsUser);
 	READ_OID_FIELD(fs_server);
 	READ_NODE_FIELD(fdw_exprs);
 	READ_NODE_FIELD(fdw_private);
@@ -3087,6 +3102,8 @@ parseNodeString(void)
 		return_value = _readAppendRelInfo();
 	else if (MATCH("RANGETBLENTRY", 13))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("RELPERMISSIONINFO", 17))
+		return_value = _readRelPermissionInfo();
 	else if (MATCH("RANGETBLFUNCTION", 16))
 		return_value = _readRangeTblFunction();
 	else if (MATCH("TABLESAMPLECLAUSE", 17))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 76606faa3e..8d60b2369d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4149,6 +4149,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5800,7 +5803,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06ad856eac..bad257655c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5998,6 +6002,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6125,6 +6130,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9cef92cab2..ceedfe3cc9 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -111,9 +112,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -268,7 +267,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -354,10 +353,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down any subquery
+ * RTEs of the current plan level that did not make into the plan tree by way
+ * of being pulled up, nor turned into SubqueryScan nodes referenced in the
+ * plan tree.  Such subqueries would not thus have had any RelPermissionInfos
+ * referenced in them merged into root->parse->relpermlist, so we must force-
+ * add them to glob->finalrelpermlist.  Failing to do so would prevent the
+ * executor from performing expected permission checks for tables mentioned in
+ * such subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -365,34 +371,14 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 
 	/*
 	 * Add the query's own RTEs to the flattened rangetable.
-	 *
-	 * At top level, we must add all RTEs so that their indexes in the
-	 * flattened rangetable match up with their original indexes.  When
-	 * recursing, we only care about extracting relation RTEs.
-	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
 	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -409,73 +395,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					MergeRelPermissionInfos(&glob->finalrelpermlist,
+											rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
-
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
 
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
+	/* Now merge the main query's RelPermissionInfos into the global list. */
+	MergeRelPermissionInfos(&glob->finalrelpermlist, root->parse->relpermlist);
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally update the flat rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(glob->finalrtable,
+									  glob->finalrelpermlist);
 }
 
 /*
@@ -484,9 +426,7 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
  * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * which are needed by EXPLAIN.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
@@ -497,6 +437,14 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
 	memcpy(newrte, rte, sizeof(RangeTblEntry));
 
+	/*
+	 * Executor may need to look up RelPermissionInfos, so must keep
+	 * perminfoindex around.  Though, the list containing the RelPermissionInfo
+	 * to which the index points will be folded into glob->finalrelpermlist, so
+	 * offset the index likewise.
+	 */
+	newrte->perminfoindex += list_length(glob->finalrelpermlist);
+
 	/* zap unneeded sub-structure */
 	newrte->tablesample = NULL;
 	newrte->subquery = NULL;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index df4ca12919..79a7a04032 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1499,6 +1499,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subselect->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..ae36b47e58 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1211,6 +1204,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1349,6 +1348,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/* Add the child query's RelPermissionInfos into the parent query. */
+	MergeRelPermissionInfos(&root->parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(root->parse->rtable,
+									  root->parse->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f3..919e2ec1e9 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..64a00b541b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RelPermissionInfo, though
+		 * only the tables mentioned in query are assigned RelPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RelPermissionInfo *perminfo =
+				GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 1bcb875507..3df097b2f1 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -550,7 +551,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -669,6 +670,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +918,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +946,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1105,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1398,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1627,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1874,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2349,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2416,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2435,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2447,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2488,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2776,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3255,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3300,9 +3320,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c655d188c7..cf1cb81586 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3233,16 +3233,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..8d920eeb7e 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RelPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 926dcbf30e..f9bcc8596d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,12 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static RelPermissionInfo *AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex);
+static RelPermissionInfo *GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok);
+static Index GetRelPermissionInfoIndex(List *relpermlist, Oid relid);
 
 
 /*
@@ -1021,10 +1027,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1244,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1286,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1430,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1467,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1476,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1497,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1558,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1567,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1588,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1639,21 +1658,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1946,20 +1959,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1971,7 +1977,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2027,20 +2033,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2115,19 +2114,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,19 +2205,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2239,6 +2226,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2362,19 +2350,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2488,16 +2470,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2509,7 +2488,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3130,6 +3109,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3147,7 +3127,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3696,3 +3679,221 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless one with the same OID is found in it
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	Assert(rte->rtekind == RTE_RELATION);
+
+	Assert(rte->perminfoindex == 0);
+	return AddRelPermissionInfoInternal(relpermlist, rte->relid,
+										&rte->perminfoindex);
+}
+
+/*
+ * AddRelPermissionInfoInternal
+ *		Sub-routine of AddRelPermissionInfo that does the actual work
+ */
+static RelPermissionInfo *
+AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex)
+{
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfoInternal(*relpermlist, relid, perminfoindex,
+											true);
+	if (perminfo)
+	{
+		Assert(*perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Note its index.  */
+	*perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Find RelPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(relpermlist))
+		elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+			 rte->relid);
+	perminfo = GetRelPermissionInfoInternal(relpermlist, rte->relid,
+											&rte->perminfoindex, false);
+	if (rte->relid != perminfo->relid)
+		elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfoInternal
+ *		Sub-routine of GetRelPermissionInfo that does the actual work
+ *
+ * If *perminfoindex is 0, the list is scanned to find one with given relid.
+ * If found, *perminfoindex is set to its 1-based index in the list.
+ *
+ * If *perminfoindex is already valid (> 0), it means that the caller expects
+ * to find the entry it's looking for at that location in the list.
+ */
+static RelPermissionInfo *
+GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	if (*perminfoindex == 0)
+	{
+		ListCell   *lc;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == relid)
+			{
+				*perminfoindex = foreach_current_index(lc) + 1;
+				return perminfo;
+			}
+		}
+	}
+	else if (*perminfoindex > 0)
+	{
+
+		perminfo = (RelPermissionInfo *)
+			list_nth(relpermlist, *perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * GetRelPermissionInfoIndex
+ *		Returns a 1-based index of the RelPermissionInfo of matching relid if
+ *		found in the given list
+ *
+ * 0 indicates that one was not found.
+ */
+static Index
+GetRelPermissionInfoIndex(List *relpermlist, Oid relid)
+{
+	ListCell   *lc;
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return foreach_current_index(lc) + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source query (src_relpermlist)
+ *		into the destination query's list (*dest_relpermlist), "merging"
+ *		properties of any that are present in both.
+ *
+ * Caller must subsequently call ReassignRangeTablePermInfoIndexes() on the
+ * source query's range table.
+ */
+void
+MergeRelPermissionInfos(List **dest_relpermlist, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *dest_perminfo;
+		Index		ignored = 0;
+
+		dest_perminfo = AddRelPermissionInfoInternal(dest_relpermlist,
+													 src_perminfo->relid,
+													 &ignored);
+
+		dest_perminfo->inh |= src_perminfo->inh;
+		dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+		if (!OidIsValid(dest_perminfo->checkAsUser))
+			dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+		dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+												src_perminfo->selectedCols);
+		dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+												src_perminfo->insertedCols);
+		dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+											   src_perminfo->updatedCols);
+		dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+													src_perminfo->extraUpdatedCols);
+	}
+}
+
+/*
+ * ReassignRangeTablePermInfoIndexes
+ * 		Updates perminfoindex of the relation RTEs in rtable so that they reflect
+ * 		their respective RelPermissionInfo entry's current position in permlist.
+ *
+ * This is provided for the sites that use MergeRelPermissionInfos() to merge
+ * the RelPermissionInfo entries of two queries.
+ */
+void
+ReassignRangeTablePermInfoIndexes(List *rtable, List *relpermlist)
+{
+	ListCell   *l;
+
+	foreach(l, rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		/*
+		 * Only RELATIONs would have been assigned a RelPermissionInfo and that
+		 * too only those that are mentioned in the query (not inheritance
+		 * child relations that are added afterwards), so we also check that
+		 * the RTE's existing index is valid.
+		 */
+		if (rte->rtekind != RTE_RELATION && rte->perminfoindex > 0)
+			continue;
+		rte->perminfoindex = GetRelPermissionInfoIndex(relpermlist, rte->relid);
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..4543949a54 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index f889726a28..fb597eb650 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1222,7 +1222,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3012,9 +3013,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3070,6 +3068,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3112,8 +3111,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 38e3b1c1b3..bda16358e6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -491,6 +492,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1787,7 +1790,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1835,7 +1838,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1845,14 +1848,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2cbca4a087..3858186e56 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1110,7 +1110,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..9144843fd5 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -787,14 +788,7 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ *		field to the given userid in all RelPermissionInfos of the query.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -821,18 +815,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RelPermissionInfos for this query. */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..fca2a1eaf0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,27 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(&sub_action->relpermlist, parsetree->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(sub_action->rtable,
+									  sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1594,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1623,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3174,6 +3150,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..d0a292d46c 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 7c02fb279f..3ff53bd159 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1560,6 +1561,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1607,10 +1609,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 51b3fdc9a0..58d35318c1 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1374,8 +1374,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1393,32 +1393,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1428,9 +1422,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fa1f589fad..c0914bf4a1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5168,7 +5168,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5220,7 +5220,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5299,7 +5299,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5347,7 +5347,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5408,6 +5408,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5416,7 +5417,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5485,7 +5486,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index f502df91dc..fe2b72a851 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -846,8 +846,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RelPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 3df1c5a97c..af40f21496 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d68a6b9d28..eb812b3308 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -602,6 +603,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5728801379..95b22bf6af 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -548,6 +548,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -595,6 +603,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;	/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ce1fc4deb..ff6d0a8f97 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -91,6 +91,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_RelPermissionInfo,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f93d866548..883d1fa81a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -148,6 +148,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -956,37 +958,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1040,11 +1011,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1164,14 +1141,60 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table, though
+ * 		different RTEs for the same relation share the RelPermissionInfo, that
+ * 		is, there is only one RelPermissionInfo containing a given relid.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b88cfb8dc0..fe03764fbb 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,8 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	List	   *finalrelpermlist;	/* "flat" list of RelPermissionInfo */
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d5c0ebe859..f1d677ce08 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -66,6 +66,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -691,6 +694,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index cf9c759025..e573b1620f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -181,6 +181,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +236,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -268,6 +271,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index de21c3c649..923add1176 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,13 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
+											   RangeTblEntry *rte);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
+											   RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(List **dest_relpermlist,
+									List *src_relpermlist);
+extern void ReassignRangeTablePermInfoIndexes(List *rtable,
+											  List *relpermlist);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..f21786da35 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.35.3



  [application/octet-stream] v14-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (122.2K, ../../CA+HiwqFOq=J=1f_8ecMY146pyups4W05MfrQx_3TVK_emzK7bA@mail.gmail.com/3-v14-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 7add63949eaf734a3a554c7fc93f7af5fb4591bb Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v14 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add an RTE for view relations.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  34 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/jsonb_sqljson.out   |  62 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/sqljson.out         |   6 +-
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 28 files changed, 758 insertions(+), 837 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 44457f930c..5913d4cee5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2485,7 +2485,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2548,7 +2548,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6423,10 +6423,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6517,10 +6517,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b97b8b0435..2cbc53b42c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f0958f03a0..2519970914 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -339,78 +339,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -573,12 +501,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fca2a1eaf0..6225ccba30 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1859,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 1f08716f69..f626e59c5d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2216,7 +2216,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2232,7 +2232,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2248,7 +2248,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2264,7 +2264,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2282,7 +2282,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3239,7 +3239,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 601047fa3d..36bed0ac1b 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1544,7 +1544,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1596,7 +1596,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1612,7 +1612,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1628,7 +1628,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2113,15 +2113,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5ede56d9b5..7315ddb92a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2475,8 +2475,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2487,8 +2487,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2514,8 +2514,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2527,8 +2527,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 32385bbb0e..e60bdf2dff 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1604,9 +1604,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1627,9 +1627,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1719,8 +1719,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1965,7 +1965,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2017,19 +2017,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index e2f7df50a8..d157e8efdc 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -1161,37 +1161,37 @@ SELECT * FROM
 	);
 \sv jsonb_table_view
 CREATE OR REPLACE VIEW public.jsonb_table_view AS
- SELECT "json_table".id,
-    "json_table".id2,
-    "json_table"."int",
-    "json_table".text,
-    "json_table"."char(4)",
-    "json_table".bool,
-    "json_table"."numeric",
-    "json_table".domain,
-    "json_table".js,
-    "json_table".jb,
-    "json_table".jst,
-    "json_table".jsc,
-    "json_table".jsv,
-    "json_table".jsb,
-    "json_table".jsbq,
-    "json_table".aaa,
-    "json_table".aaa1,
-    "json_table".exists1,
-    "json_table".exists2,
-    "json_table".exists3,
-    "json_table".js2,
-    "json_table".jsb2w,
-    "json_table".jsb2q,
-    "json_table".ia,
-    "json_table".ta,
-    "json_table".jba,
-    "json_table".a1,
-    "json_table".b1,
-    "json_table".a11,
-    "json_table".a21,
-    "json_table".a22
+ SELECT id,
+    id2,
+    "int",
+    text,
+    "char(4)",
+    bool,
+    "numeric",
+    domain,
+    js,
+    jb,
+    jst,
+    jsc,
+    jsv,
+    jsb,
+    jsbq,
+    aaa,
+    aaa1,
+    exists1,
+    exists2,
+    exists3,
+    js2,
+    jsb2w,
+    jsb2q,
+    ia,
+    ta,
+    jba,
+    a1,
+    b1,
+    a11,
+    a21,
+    a22
    FROM JSON_TABLE(
             'null'::jsonb, '$[*]' AS json_table_path_1
             PASSING
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 2334a1321e..2c4da34687 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7ec3d2688f..fc35d8a567 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1453,10 +1453,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1702,23 +1702,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1732,10 +1732,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1803,13 +1803,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1822,57 +1822,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1895,8 +1895,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1905,13 +1905,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2021,16 +2021,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2068,26 +2068,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2106,41 +2106,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2150,68 +2150,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2228,19 +2228,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2250,19 +2250,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2306,64 +2306,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2548,24 +2548,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3085,7 +3085,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3124,8 +3124,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3137,8 +3137,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3150,8 +3150,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3163,8 +3163,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 0883261535..52c71d7ad7 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1049,7 +1049,7 @@ SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING t
 FROM generate_series(1,5) i;
 \sv json_objectagg_view
 CREATE OR REPLACE VIEW public.json_objectagg_view AS
- SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg"
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_objectagg_view;
 -- Test JSON_ARRAYAGG deparsing
@@ -1085,7 +1085,7 @@ SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text
 FROM generate_series(1,5) i;
 \sv json_arrayagg_view
 CREATE OR REPLACE VIEW public.json_arrayagg_view AS
- SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg"
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_arrayagg_view;
 -- Test JSON_ARRAY(subquery) deparsing
@@ -1303,7 +1303,7 @@ SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT
 \sv is_json_view
 CREATE OR REPLACE VIEW public.is_json_view AS
  SELECT '1'::text IS JSON AS "any",
-    ('1'::text || i.i) IS JSON SCALAR AS scalar,
+    ('1'::text || i) IS JSON SCALAR AS scalar,
     NOT '[]'::text IS JSON ARRAY AS "array",
     '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
    FROM generate_series(1, 3) i(i)
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index cd812336f2..b09603dc98 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 433a0bb025..7a9968afe2 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 30dd900e11..adbe32c32e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 55ac49be26..73a3a52562 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 493c6186e1..b524e1665f 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-07-13 08:00  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-07-13 08:00 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

Rebased over 964d01ae90.


--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v15-0001-Rework-query-relation-permission-checking.patch (150.2K, ../../CA+HiwqH8AOfz0jkQ_SMO3WD7CczOK+A74PFf7G2O3CBacHWrLg@mail.gmail.com/2-v15-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From c76ce37c8d19be1dff76e6f7f988be7085abf075 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v15 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table to look for any
RTE_RELATION entries to have permissions checked.  This arrangement
makes permissions-checking needlessly expensive when many inheritance
children are added to the range range, because while their permissions
need not be checked, the only way to find that out is by seeing
requiredPerms == 0 in the their RTEs.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RelPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the entry's index in the query's list of
RelPermissionInfos.
---
 contrib/postgres_fdw/postgres_fdw.c         |  81 +++--
 contrib/sepgsql/dml.c                       |  42 +--
 contrib/sepgsql/hooks.c                     |   6 +-
 src/backend/access/common/attmap.c          |  13 +-
 src/backend/access/common/tupconvert.c      |   2 +-
 src/backend/catalog/partition.c             |   3 +-
 src/backend/commands/copy.c                 |  18 +-
 src/backend/commands/copyfrom.c             |   9 +
 src/backend/commands/indexcmds.c            |   3 +-
 src/backend/commands/tablecmds.c            |  24 +-
 src/backend/commands/view.c                 |   6 +-
 src/backend/executor/execMain.c             | 105 +++---
 src/backend/executor/execParallel.c         |   1 +
 src/backend/executor/execPartition.c        |  15 +-
 src/backend/executor/execUtils.c            | 159 ++++++---
 src/backend/nodes/outfuncs.c                |   8 +-
 src/backend/nodes/readfuncs.c               |   8 +-
 src/backend/optimizer/plan/createplan.c     |   6 +-
 src/backend/optimizer/plan/planner.c        |   6 +
 src/backend/optimizer/plan/setrefs.c        | 123 ++-----
 src/backend/optimizer/plan/subselect.c      |   6 +
 src/backend/optimizer/prep/prepjointree.c   |  20 +-
 src/backend/optimizer/util/inherit.c        | 170 +++++++---
 src/backend/optimizer/util/relnode.c        |  21 +-
 src/backend/parser/analyze.c                |  60 +++-
 src/backend/parser/parse_clause.c           |   9 +-
 src/backend/parser/parse_merge.c            |   9 +-
 src/backend/parser/parse_relation.c         | 357 +++++++++++++++-----
 src/backend/parser/parse_target.c           |  19 +-
 src/backend/parser/parse_utilcmd.c          |   9 +-
 src/backend/replication/logical/worker.c    |  13 +-
 src/backend/replication/pgoutput/pgoutput.c |   2 +-
 src/backend/rewrite/rewriteDefine.c         |  25 +-
 src/backend/rewrite/rewriteHandler.c        | 189 +++++------
 src/backend/rewrite/rowsecurity.c           |  24 +-
 src/backend/statistics/extended_stats.c     |   7 +-
 src/backend/utils/adt/ri_triggers.c         |  34 +-
 src/backend/utils/adt/selfuncs.c            |  13 +-
 src/backend/utils/cache/relcache.c          |   4 +-
 src/include/access/attmap.h                 |   6 +-
 src/include/commands/copyfrom_internal.h    |   3 +-
 src/include/executor/executor.h             |   7 +-
 src/include/nodes/execnodes.h               |   9 +
 src/include/nodes/parsenodes.h              |  90 +++--
 src/include/nodes/pathnodes.h               |   3 +
 src/include/nodes/plannodes.h               |   4 +
 src/include/optimizer/inherit.h             |   1 +
 src/include/parser/parse_node.h             |   6 +-
 src/include/parser/parse_relation.h         |   8 +
 src/include/rewrite/rewriteHandler.h        |   2 +-
 50 files changed, 1089 insertions(+), 679 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 955a428e3d..b932d3a5ed 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1934,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1942,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1966,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1978,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2145,6 +2180,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2222,6 +2258,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2238,7 +2276,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2624,7 +2663,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2644,13 +2682,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3945,12 +3982,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3962,12 +3999,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..2cf75b6a6d 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..cb9aeb175d 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -288,17 +288,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..7bc85d9eb5 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3ac731803b..b56b7b4bda 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a976008b3d..3407486bb3 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1383,7 +1389,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ff847579f3..a88bf410cb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1279,7 +1279,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ef5b34a312..f05f26e5f0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1200,7 +1200,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9651,7 +9652,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9818,7 +9820,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10024,7 +10027,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10202,7 +10206,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12296,7 +12301,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18048,7 +18054,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18975,7 +18982,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c6..f0958f03a0 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,7 +353,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -397,10 +397,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..836e5ef344 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,7 +74,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -567,38 +567,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -606,32 +607,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f1fd7f7e8b..a89ab74a7e 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index e03ea27299..85a1dc84a6 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -781,7 +783,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -879,7 +882,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1141,7 +1145,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..e9cf39dafb 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4d776e7b51..fe91161774 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -463,6 +463,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(isReturn);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(relpermlist);
 	WRITE_NODE_FIELD(jointree);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_ENUM_FIELD(override, OverridingKind);
@@ -505,6 +506,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -558,12 +560,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1421686938..7f2bd79229 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(isReturn);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
+	READ_NODE_FIELD(relpermlist);
 	READ_NODE_FIELD(jointree);
 	READ_NODE_FIELD(targetList);
 	READ_ENUM_FIELD(override, OverridingKind);
@@ -352,6 +353,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -415,12 +417,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index e37f2933eb..a4a6b3986b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5799,7 +5802,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06ad856eac..bad257655c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5998,6 +6002,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6125,6 +6130,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9cef92cab2..76d925c4ce 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -111,9 +112,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -268,7 +267,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -354,10 +353,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down subquery RTEs
+ * of the current plan level whose query was not pulled up into the parent
+ * query, nor turned into SubqueryScan nodes referenced in the plan tree. Such
+ * subqueries would not thus have had their RelPermissionInfos merged into
+ * root->parse->relpermlist, so we must force-add them to
+ * glob->finalrelpermlist.  Failing to do so would prevent the executor from
+ * performing expected permission checks for tables mentioned in such
+ * subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -365,34 +371,14 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 
 	/*
 	 * Add the query's own RTEs to the flattened rangetable.
-	 *
-	 * At top level, we must add all RTEs so that their indexes in the
-	 * flattened rangetable match up with their original indexes.  When
-	 * recursing, we only care about extracting relation RTEs.
-	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
 	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -409,73 +395,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					MergeRelPermissionInfos(&glob->finalrelpermlist,
+											rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
 
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
-
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
+	/* Now merge the main query's RelPermissionInfos into the global list. */
+	MergeRelPermissionInfos(&glob->finalrelpermlist, root->parse->relpermlist);
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally update the flat rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(glob->finalrtable,
+									  glob->finalrelpermlist);
 }
 
 /*
@@ -483,10 +425,9 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to check the relation's permissions.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index df4ca12919..79a7a04032 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1499,6 +1499,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subselect->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..ae36b47e58 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1211,6 +1204,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1349,6 +1348,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/* Add the child query's RelPermissionInfos into the parent query. */
+	MergeRelPermissionInfos(&root->parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(root->parse->rtable,
+									  root->parse->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f3..919e2ec1e9 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..64a00b541b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RelPermissionInfo, though
+		 * only the tables mentioned in query are assigned RelPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RelPermissionInfo *perminfo =
+				GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 8ed2c4b8c7..e02174fc79 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -550,7 +551,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -669,6 +670,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +918,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +946,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1105,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1398,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1627,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1874,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2349,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2416,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2435,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2447,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2488,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2776,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3255,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3317,9 +3337,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c655d188c7..cf1cb81586 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3233,16 +3233,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..8d920eeb7e 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RelPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 926dcbf30e..f9bcc8596d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,12 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static RelPermissionInfo *AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex);
+static RelPermissionInfo *GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok);
+static Index GetRelPermissionInfoIndex(List *relpermlist, Oid relid);
 
 
 /*
@@ -1021,10 +1027,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1244,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1286,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1430,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1467,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1476,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1497,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1558,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1567,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1588,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1639,21 +1658,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1946,20 +1959,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1971,7 +1977,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2027,20 +2033,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2115,19 +2114,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2212,19 +2205,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2239,6 +2226,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2362,19 +2350,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2488,16 +2470,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2509,7 +2488,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3130,6 +3109,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3147,7 +3127,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3696,3 +3679,221 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless one with the same OID is found in it
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	Assert(rte->rtekind == RTE_RELATION);
+
+	Assert(rte->perminfoindex == 0);
+	return AddRelPermissionInfoInternal(relpermlist, rte->relid,
+										&rte->perminfoindex);
+}
+
+/*
+ * AddRelPermissionInfoInternal
+ *		Sub-routine of AddRelPermissionInfo that does the actual work
+ */
+static RelPermissionInfo *
+AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex)
+{
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfoInternal(*relpermlist, relid, perminfoindex,
+											true);
+	if (perminfo)
+	{
+		Assert(*perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Note its index.  */
+	*perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Find RelPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(relpermlist))
+		elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+			 rte->relid);
+	perminfo = GetRelPermissionInfoInternal(relpermlist, rte->relid,
+											&rte->perminfoindex, false);
+	if (rte->relid != perminfo->relid)
+		elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfoInternal
+ *		Sub-routine of GetRelPermissionInfo that does the actual work
+ *
+ * If *perminfoindex is 0, the list is scanned to find one with given relid.
+ * If found, *perminfoindex is set to its 1-based index in the list.
+ *
+ * If *perminfoindex is already valid (> 0), it means that the caller expects
+ * to find the entry it's looking for at that location in the list.
+ */
+static RelPermissionInfo *
+GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	if (*perminfoindex == 0)
+	{
+		ListCell   *lc;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == relid)
+			{
+				*perminfoindex = foreach_current_index(lc) + 1;
+				return perminfo;
+			}
+		}
+	}
+	else if (*perminfoindex > 0)
+	{
+
+		perminfo = (RelPermissionInfo *)
+			list_nth(relpermlist, *perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * GetRelPermissionInfoIndex
+ *		Returns a 1-based index of the RelPermissionInfo of matching relid if
+ *		found in the given list
+ *
+ * 0 indicates that one was not found.
+ */
+static Index
+GetRelPermissionInfoIndex(List *relpermlist, Oid relid)
+{
+	ListCell   *lc;
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return foreach_current_index(lc) + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source query (src_relpermlist)
+ *		into the destination query's list (*dest_relpermlist), "merging"
+ *		properties of any that are present in both.
+ *
+ * Caller must subsequently call ReassignRangeTablePermInfoIndexes() on the
+ * source query's range table.
+ */
+void
+MergeRelPermissionInfos(List **dest_relpermlist, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *dest_perminfo;
+		Index		ignored = 0;
+
+		dest_perminfo = AddRelPermissionInfoInternal(dest_relpermlist,
+													 src_perminfo->relid,
+													 &ignored);
+
+		dest_perminfo->inh |= src_perminfo->inh;
+		dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+		if (!OidIsValid(dest_perminfo->checkAsUser))
+			dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+		dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+												src_perminfo->selectedCols);
+		dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+												src_perminfo->insertedCols);
+		dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+											   src_perminfo->updatedCols);
+		dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+													src_perminfo->extraUpdatedCols);
+	}
+}
+
+/*
+ * ReassignRangeTablePermInfoIndexes
+ * 		Updates perminfoindex of the relation RTEs in rtable so that they reflect
+ * 		their respective RelPermissionInfo entry's current position in permlist.
+ *
+ * This is provided for the sites that use MergeRelPermissionInfos() to merge
+ * the RelPermissionInfo entries of two queries.
+ */
+void
+ReassignRangeTablePermInfoIndexes(List *rtable, List *relpermlist)
+{
+	ListCell   *l;
+
+	foreach(l, rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		/*
+		 * Only RELATIONs would have been assigned a RelPermissionInfo and that
+		 * too only those that are mentioned in the query (not inheritance
+		 * child relations that are added afterwards), so we also check that
+		 * the RTE's existing index is valid.
+		 */
+		if (rte->rtekind != RTE_RELATION && rte->perminfoindex > 0)
+			continue;
+		rte->perminfoindex = GetRelPermissionInfoIndex(relpermlist, rte->relid);
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..4543949a54 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b57253463b..e90f2162ca 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1222,7 +1222,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3012,9 +3013,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3070,6 +3068,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3112,8 +3111,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 38e3b1c1b3..bda16358e6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -491,6 +492,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1787,7 +1790,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1835,7 +1838,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1845,14 +1848,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2cbca4a087..3858186e56 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1110,7 +1110,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 185bf5fbff..9144843fd5 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -787,14 +788,7 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ *		field to the given userid in all RelPermissionInfos of the query.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -821,18 +815,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RelPermissionInfos for this query. */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..fca2a1eaf0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,27 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(&sub_action->relpermlist, parsetree->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(sub_action->rtable,
+									  sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1594,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1623,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3174,6 +3150,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..d0a292d46c 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index d2aa8d0ca3..d501947912 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1560,6 +1561,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1607,10 +1609,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 51b3fdc9a0..58d35318c1 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1374,8 +1374,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1393,32 +1393,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1428,9 +1422,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fa1f589fad..c0914bf4a1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5168,7 +5168,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5220,7 +5220,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5299,7 +5299,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5347,7 +5347,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5408,6 +5408,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5416,7 +5417,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5485,7 +5486,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bdb771d278..fd53780908 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -846,8 +846,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RelPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 3df1c5a97c..af40f21496 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d68a6b9d28..eb812b3308 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -602,6 +603,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..25ae278deb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;		/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e2ad761768..a061a49354 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -153,6 +153,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -967,37 +969,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1053,11 +1024,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1177,14 +1154,61 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table, though
+ * 		different RTEs for the same relation share the RelPermissionInfo, that
+ * 		is, there is only one RelPermissionInfo containing a given relation
+ * 		OID (relid).
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 44ffc73f15..9ab9f9df51 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RelPermissionInfos */
+	List	   *finalrelpermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index dca2a21e7a..6417b3cac2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -71,6 +71,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -702,6 +705,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index cf9c759025..e573b1620f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -181,6 +181,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +236,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -268,6 +271,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index de21c3c649..923add1176 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,13 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
+											   RangeTblEntry *rte);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
+											   RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(List **dest_relpermlist,
+									List *src_relpermlist);
+extern void ReassignRangeTablePermInfoIndexes(List *rtable,
+											  List *relpermlist);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..f21786da35 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.35.3



  [application/octet-stream] v15-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (122.2K, ../../CA+HiwqH8AOfz0jkQ_SMO3WD7CczOK+A74PFf7G2O3CBacHWrLg@mail.gmail.com/3-v15-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 714d1bfef685258c3a3168c5bb72361c4a3059e7 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v15 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add an RTE for view relations.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  34 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 210 ++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/jsonb_sqljson.out   |  62 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/sqljson.out         |   6 +-
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 28 files changed, 758 insertions(+), 837 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 5f2ef88cf3..9902843bf9 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2485,7 +2485,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2548,7 +2548,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6423,10 +6423,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6517,10 +6517,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b97b8b0435..2cbc53b42c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f0958f03a0..2519970914 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -339,78 +339,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -573,12 +501,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fca2a1eaf0..6225ccba30 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1859,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 1f08716f69..f626e59c5d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2216,7 +2216,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2232,7 +2232,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2248,7 +2248,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2264,7 +2264,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2282,7 +2282,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3239,7 +3239,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 601047fa3d..36bed0ac1b 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1544,7 +1544,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1596,7 +1596,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1612,7 +1612,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1628,7 +1628,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2113,15 +2113,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5ede56d9b5..7315ddb92a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2475,8 +2475,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2487,8 +2487,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2514,8 +2514,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2527,8 +2527,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 32385bbb0e..e60bdf2dff 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1604,9 +1604,9 @@ alter table tt14t drop column f3;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1627,9 +1627,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1719,8 +1719,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -1965,7 +1965,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2017,19 +2017,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index e2f7df50a8..d157e8efdc 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -1161,37 +1161,37 @@ SELECT * FROM
 	);
 \sv jsonb_table_view
 CREATE OR REPLACE VIEW public.jsonb_table_view AS
- SELECT "json_table".id,
-    "json_table".id2,
-    "json_table"."int",
-    "json_table".text,
-    "json_table"."char(4)",
-    "json_table".bool,
-    "json_table"."numeric",
-    "json_table".domain,
-    "json_table".js,
-    "json_table".jb,
-    "json_table".jst,
-    "json_table".jsc,
-    "json_table".jsv,
-    "json_table".jsb,
-    "json_table".jsbq,
-    "json_table".aaa,
-    "json_table".aaa1,
-    "json_table".exists1,
-    "json_table".exists2,
-    "json_table".exists3,
-    "json_table".js2,
-    "json_table".jsb2w,
-    "json_table".jsb2q,
-    "json_table".ia,
-    "json_table".ta,
-    "json_table".jba,
-    "json_table".a1,
-    "json_table".b1,
-    "json_table".a11,
-    "json_table".a21,
-    "json_table".a22
+ SELECT id,
+    id2,
+    "int",
+    text,
+    "char(4)",
+    bool,
+    "numeric",
+    domain,
+    js,
+    jb,
+    jst,
+    jsc,
+    jsv,
+    jsb,
+    jsbq,
+    aaa,
+    aaa1,
+    exists1,
+    exists2,
+    exists3,
+    js2,
+    jsb2w,
+    jsb2q,
+    ia,
+    ta,
+    jba,
+    a1,
+    b1,
+    a11,
+    a21,
+    a22
    FROM JSON_TABLE(
             'null'::jsonb, '$[*]' AS json_table_path_1
             PASSING
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 2334a1321e..2c4da34687 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7ec3d2688f..fc35d8a567 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1453,10 +1453,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1702,23 +1702,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1732,10 +1732,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1803,13 +1803,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1822,57 +1822,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1895,8 +1895,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1905,13 +1905,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2021,16 +2021,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2068,26 +2068,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2106,41 +2106,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2150,68 +2150,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2228,19 +2228,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2250,19 +2250,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2306,64 +2306,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2548,24 +2548,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3085,7 +3085,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3124,8 +3124,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3137,8 +3137,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3150,8 +3150,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3163,8 +3163,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index aae4ba4939..a9a39900f2 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1059,7 +1059,7 @@ SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING t
 FROM generate_series(1,5) i;
 \sv json_objectagg_view
 CREATE OR REPLACE VIEW public.json_objectagg_view AS
- SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg"
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_objectagg_view;
 -- Test JSON_ARRAYAGG deparsing
@@ -1095,7 +1095,7 @@ SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text
 FROM generate_series(1,5) i;
 \sv json_arrayagg_view
 CREATE OR REPLACE VIEW public.json_arrayagg_view AS
- SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg"
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_arrayagg_view;
 -- Test JSON_ARRAY(subquery) deparsing
@@ -1313,7 +1313,7 @@ SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT
 \sv is_json_view
 CREATE OR REPLACE VIEW public.is_json_view AS
  SELECT '1'::text IS JSON AS "any",
-    ('1'::text || i.i) IS JSON SCALAR AS scalar,
+    ('1'::text || i) IS JSON SCALAR AS scalar,
     NOT '[]'::text IS JSON ARRAY AS "array",
     '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
    FROM generate_series(1, 3) i(i)
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index cd812336f2..b09603dc98 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 433a0bb025..7a9968afe2 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 30dd900e11..adbe32c32e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 55ac49be26..73a3a52562 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 493c6186e1..b524e1665f 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-07-27 03:14  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-07-27 03:14 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Jul 13, 2022 at 5:00 PM Amit Langote <[email protected]> wrote:
> Rebased over 964d01ae90.

Rebased over 2d04277121f.


--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v16-0001-Rework-query-relation-permission-checking.patch (149.7K, ../../CA+HiwqGKnrnzZQB8xwW-fLVUbURQmvY9rBSyJ6arR2YO7KPFVA@mail.gmail.com/2-v16-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 4923c4f8738df730c71616c21245557d0bb3ed41 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v16 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table to look for any
RTE_RELATION entries to have permissions checked.  This arrangement
makes permissions-checking needlessly expensive when many inheritance
children are added to the range range, because while their permissions
need not be checked, the only way to find that out is by seeing
requiredPerms == 0 in the their RTEs.

This commit moves the permission checking information out of the
range table entries into a new plan node called RelPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RelPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the entry's index in the query's list of
RelPermissionInfos.
---
 contrib/postgres_fdw/postgres_fdw.c         |  81 +++--
 contrib/sepgsql/dml.c                       |  42 +--
 contrib/sepgsql/hooks.c                     |   6 +-
 src/backend/access/common/attmap.c          |  13 +-
 src/backend/access/common/tupconvert.c      |   2 +-
 src/backend/catalog/partition.c             |   3 +-
 src/backend/commands/copy.c                 |  18 +-
 src/backend/commands/copyfrom.c             |   9 +
 src/backend/commands/indexcmds.c            |   3 +-
 src/backend/commands/tablecmds.c            |  24 +-
 src/backend/commands/view.c                 |   6 +-
 src/backend/executor/execMain.c             | 105 +++---
 src/backend/executor/execParallel.c         |   1 +
 src/backend/executor/execPartition.c        |  15 +-
 src/backend/executor/execUtils.c            | 159 ++++++---
 src/backend/nodes/outfuncs.c                |   7 +-
 src/backend/nodes/readfuncs.c               |   7 +-
 src/backend/optimizer/plan/createplan.c     |   6 +-
 src/backend/optimizer/plan/planner.c        |   6 +
 src/backend/optimizer/plan/setrefs.c        | 123 ++-----
 src/backend/optimizer/plan/subselect.c      |   6 +
 src/backend/optimizer/prep/prepjointree.c   |  20 +-
 src/backend/optimizer/util/inherit.c        | 170 +++++++---
 src/backend/optimizer/util/relnode.c        |  21 +-
 src/backend/parser/analyze.c                |  60 +++-
 src/backend/parser/parse_clause.c           |   9 +-
 src/backend/parser/parse_merge.c            |   9 +-
 src/backend/parser/parse_relation.c         | 357 +++++++++++++++-----
 src/backend/parser/parse_target.c           |  19 +-
 src/backend/parser/parse_utilcmd.c          |   9 +-
 src/backend/replication/logical/worker.c    |  13 +-
 src/backend/replication/pgoutput/pgoutput.c |   2 +-
 src/backend/rewrite/rewriteDefine.c         |  25 +-
 src/backend/rewrite/rewriteHandler.c        | 189 +++++------
 src/backend/rewrite/rowsecurity.c           |  24 +-
 src/backend/statistics/extended_stats.c     |   7 +-
 src/backend/utils/adt/ri_triggers.c         |  34 +-
 src/backend/utils/adt/selfuncs.c            |  13 +-
 src/backend/utils/cache/relcache.c          |   4 +-
 src/include/access/attmap.h                 |   6 +-
 src/include/commands/copyfrom_internal.h    |   3 +-
 src/include/executor/executor.h             |   7 +-
 src/include/nodes/execnodes.h               |   9 +
 src/include/nodes/parsenodes.h              |  90 +++--
 src/include/nodes/pathnodes.h               |   3 +
 src/include/nodes/plannodes.h               |   4 +
 src/include/optimizer/inherit.h             |   1 +
 src/include/parser/parse_node.h             |   6 +-
 src/include/parser/parse_relation.h         |   8 +
 src/include/rewrite/rewriteHandler.h        |   2 +-
 50 files changed, 1087 insertions(+), 679 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 048db542d3..793612de30 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,35 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The way the user is chosen matches what ExecCheckPermissions() does.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1934,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1942,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1966,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1978,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2145,6 +2180,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2222,6 +2258,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2238,7 +2276,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2624,7 +2663,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2644,13 +2682,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3953,12 +3990,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3970,12 +4007,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..2cf75b6a6d 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -277,38 +277,32 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *relpermlist, bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, relpermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RelPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -320,13 +314,13 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		/*
 		 * If this RangeTblEntry is also supposed to reference inherited
 		 * tables, we need to check security label of the child tables. So, we
-		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
+		 * expand perminfo->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +333,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..603f3928e9 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *relpermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (relpermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(relpermlist, abort))
 		return false;
 
 	return true;
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..7bc85d9eb5 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,14 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +239,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +261,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3ac731803b..b56b7b4bda 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RelPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,10 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->relid = relid;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +156,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +178,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a976008b3d..3407486bb3 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the relation permissions into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_relpermlist = cstate->relpermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1383,7 +1389,10 @@ BeginCopyFrom(ParseState *pstate,
 
 	/* Assign range table, we'll need it in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->relpermlist = pstate->p_relpermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 15a57ea9c3..0d8274d5a7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1279,7 +1279,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7fbee0c1f7..7a8274ec0a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1204,7 +1204,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9616,7 +9617,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9783,7 +9785,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -9989,7 +9992,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10167,7 +10171,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12260,7 +12265,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18006,7 +18012,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18933,7 +18940,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 8690a3f3c6..f0958f03a0 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,7 +353,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -397,10 +397,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..836e5ef344 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,7 +74,7 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
+/* Hook for plugin to get control in ExecCheckPermissions() */
 ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
 
 /* decls for local routines only used within this module */
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RelPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -567,38 +567,39 @@ ExecutorRewind(QueryDesc *queryDesc)
  * See rewrite/rowsecurity.c.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
 	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
+		result = (*ExecutorCheckPerms_hook) (relpermlist,
 											 ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RelPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
@@ -606,32 +607,21 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	Oid			relOid;
 	Oid			userid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	relOid = perminfo->relid;
+	Assert(OidIsValid(relOid));
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->relpermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(plannedstmt->relpermlist, true);
+	estate->es_relpermlist = plannedstmt->relpermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f1fd7f7e8b..a89ab74a7e 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relpermlist = NIL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index e03ea27299..85a1dc84a6 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -781,7 +783,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -879,7 +882,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1141,7 +1145,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..e9cf39dafb 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are ignored, something that's
+			 * allowed with traditional inheritance.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRelPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RelPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RelPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RelPermissionInfo *
+GetResultRelPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,60 +1318,79 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRelPermissionInfo(estate->es_relpermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RelPermissionInfo *perminfo = GetResultRelPermissionInfo(relinfo, estate);
 
-		return rte->extraUpdatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
-		else
-			return rte->extraUpdatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->extraUpdatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->extraUpdatedCols;
 }
 
 /* Return columns being updated, including generated columns */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index a96f2ee8c6..48b72e25b1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -475,6 +475,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -528,12 +529,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bee62fc15c..2d22d110d7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -302,6 +302,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -365,12 +366,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index e37f2933eb..a4a6b3986b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5799,7 +5802,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06ad856eac..bad257655c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -305,6 +306,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrelpermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -492,6 +494,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrelpermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -519,6 +522,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->relpermlist = glob->finalrelpermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -5998,6 +6002,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6125,6 +6130,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRelPermissionInfo(&query->relpermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..c2fcf6f265 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -111,9 +112,7 @@ typedef struct
 #define fix_scan_list(root, lst, rtoffset, num_exec) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec))
 
-static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
-static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
+static void add_rtes_to_flat_rtable(PlannerInfo *root);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
@@ -268,7 +267,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 	 * will have their rangetable indexes increased by rtoffset.  (Additional
 	 * RTEs, not referenced by the Plan tree, might get added after those.)
 	 */
-	add_rtes_to_flat_rtable(root, false);
+	add_rtes_to_flat_rtable(root);
 
 	/*
 	 * Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -354,10 +353,17 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 /*
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
- * This can recurse into subquery plans; "recursing" is true if so.
+ * Does the same for RelPermissionInfos.  This also hunts down subquery RTEs
+ * of the current plan level whose query was not pulled up into the parent
+ * query, nor turned into SubqueryScan nodes referenced in the plan tree. Such
+ * subqueries would not thus have had their RelPermissionInfos merged into
+ * root->parse->relpermlist, so we must force-add them to
+ * glob->finalrelpermlist.  Failing to do so would prevent the executor from
+ * performing expected permission checks for tables mentioned in such
+ * subqueries.
  */
 static void
-add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
+add_rtes_to_flat_rtable(PlannerInfo *root)
 {
 	PlannerGlobal *glob = root->glob;
 	Index		rti;
@@ -365,34 +371,14 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 
 	/*
 	 * Add the query's own RTEs to the flattened rangetable.
-	 *
-	 * At top level, we must add all RTEs so that their indexes in the
-	 * flattened rangetable match up with their original indexes.  When
-	 * recursing, we only care about extracting relation RTEs.
-	 */
-	foreach(lc, root->parse->rtable)
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
-
-		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-	}
-
-	/*
-	 * If there are any dead subqueries, they are not referenced in the Plan
-	 * tree, so we must add RTEs contained in them to the flattened rtable
-	 * separately.  (If we failed to do this, the executor would not perform
-	 * expected permission checks for tables mentioned in such subqueries.)
-	 *
-	 * Note: this pass over the rangetable can't be combined with the previous
-	 * one, because that would mess up the numbering of the live RTEs in the
-	 * flattened rangetable.
 	 */
 	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
+		add_rte_to_flat_rtable(glob, rte);
+
 		/*
 		 * We should ignore inheritance-parent RTEs: their contents have been
 		 * pulled up into our rangetable already.  Also ignore any subquery
@@ -409,73 +395,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				Assert(rel->relid == rti);	/* sanity check on array */
 
 				/*
-				 * The subquery might never have been planned at all, if it
+				 * A dead subquery is one that was not planned at all, if it
 				 * was excluded on the basis of self-contradictory constraints
-				 * in our query level.  In this case apply
-				 * flatten_unplanned_rtes.
-				 *
-				 * If it was planned but the result rel is dummy, we assume
-				 * that it has been omitted from our plan tree (see
-				 * set_subquery_pathlist), and recurse to pull up its RTEs.
-				 *
-				 * Otherwise, it should be represented by a SubqueryScan node
-				 * somewhere in our plan tree, and we'll pull up its RTEs when
-				 * we process that plan node.
-				 *
-				 * However, if we're recursing, then we should pull up RTEs
-				 * whether the subquery is dummy or not, because we've found
-				 * that some upper query level is treating this one as dummy,
-				 * and so we won't scan this level's plan tree at all.
+				 * in our query level, or one that was planned but the result
+				 * rel was dummy.
 				 */
-				if (rel->subroot == NULL)
-					flatten_unplanned_rtes(glob, rte);
-				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
-													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+				if (rte->subquery && rte->subquery->relpermlist &&
+					(rel->subroot == NULL ||
+					 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+												  UPPERREL_FINAL, NULL))))
+					MergeRelPermissionInfos(&glob->finalrelpermlist,
+											rte->subquery->relpermlist);
 			}
+
 		}
 		rti++;
 	}
-}
 
-/*
- * Extract RangeTblEntries from a subquery that was never planned at all
- */
-static void
-flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
-{
-	/* Use query_tree_walker to find all RTEs in the parse tree */
-	(void) query_tree_walker(rte->subquery,
-							 flatten_rtes_walker,
-							 (void *) glob,
-							 QTW_EXAMINE_RTES_BEFORE);
-}
-
-static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, RangeTblEntry))
-	{
-		RangeTblEntry *rte = (RangeTblEntry *) node;
+	/* Now merge the main query's RelPermissionInfos into the global list. */
+	MergeRelPermissionInfos(&glob->finalrelpermlist, root->parse->relpermlist);
 
-		/* As above, we need only save relation RTEs */
-		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		return query_tree_walker((Query *) node,
-								 flatten_rtes_walker,
-								 (void *) glob,
-								 QTW_EXAMINE_RTES_BEFORE);
-	}
-	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+	/* Finally update the flat rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(glob->finalrtable,
+									  glob->finalrelpermlist);
 }
 
 /*
@@ -483,10 +425,9 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to check the relation's permissions.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index df4ca12919..79a7a04032 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1499,6 +1499,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subselect->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..ae36b47e58 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1211,6 +1204,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->rtable = list_concat(parse->rtable, subquery->rtable);
 
+	/* Add subquery's RelPermissionInfos into the upper query. */
+	MergeRelPermissionInfos(&parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(parse->rtable, parse->relpermlist);
+
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1349,6 +1348,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 	 */
 	root->parse->rtable = list_concat(root->parse->rtable, rtable);
 
+	/* Add the child query's RelPermissionInfos into the parent query. */
+	MergeRelPermissionInfos(&root->parse->relpermlist, subquery->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(root->parse->rtable,
+									  root->parse->relpermlist);
+
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
 	 * AppendRelInfo nodes for leaf subqueries to the parent's
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f3..919e2ec1e9 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,8 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +134,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RelPermissionInfo *root_perminfo =
+			GetRelPermissionInfo(root->parse->relpermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   root_perminfo->extraUpdatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +314,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -323,20 +334,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	Assert(partdesc);
 
 	/*
-	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * Note down whether any partition key cols are being updated.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -402,9 +409,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   child_extraUpdatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -439,7 +460,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -451,17 +471,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -471,12 +489,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,34 +556,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
-	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	}
-	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,3 +855,82 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * If it's a simple "base" rel, can just fetch its RelPermissionInfo and
+	 * get the needed columns from there.  For "other" rels, must look up the
+	 * root parent relation mentioned in the query, because only that one
+	 * gets assigned a RelPermissionInfo, and translate the columns found
+	 * there to match the input relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	perminfo = GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+	{
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_recurse(root, rel->relid,
+													   perminfo->extraUpdatedCols,
+													   rel->top_parent_relids);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = perminfo->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..64a00b541b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RelPermissionInfo, though
+		 * only the tables mentioned in query are assigned RelPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RelPermissionInfo *perminfo =
+				GetRelPermissionInfo(root->parse->relpermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..a0f28222a8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -550,7 +551,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RelPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -669,6 +670,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+
+		/*
+		 * Using the value of pstate->p_relpermlist after setTargetTable() has
+		 * been performed such that the target relation's RelPermissionInfo
+		 * is already present in it.
+		 */
+		sub_pstate->p_relpermlist = pstate->p_relpermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +902,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +918,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +946,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1105,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1398,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1627,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1874,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2349,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	Assert(pstate->p_relpermlist == NIL);
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2416,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2435,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2447,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2488,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2776,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3255,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RelPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3344,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RelPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRelPermissionInfo(qry->relpermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 5a18107e79..0f63c1a55a 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3227,16 +3227,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RelPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..8d920eeb7e 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->relpermlist = pstate->p_relpermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RelPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 8d832efc62..7f78079cd0 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,12 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static RelPermissionInfo *AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex);
+static RelPermissionInfo *GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok);
+static Index GetRelPermissionInfoIndex(List *relpermlist, Oid relid);
 
 
 /*
@@ -1021,10 +1027,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RelPermissionInfo *perminfo =
+			GetRelPermissionInfo(pstate->p_relpermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1244,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RelPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1286,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1430,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1467,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1476,14 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1497,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1534,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RelPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1558,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1567,14 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRelPermissionInfo(&pstate->p_relpermlist, rte);
+	perminfo->inh |= inh;
+
+	/*
+	 * Using |=, not = just in case the permissions entry is shared with
+	 * another RT entry for the same table.
+	 */
+	perminfo->requiredPerms |= ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1588,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1662,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1957,20 +1970,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1982,7 +1988,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2038,20 +2044,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2126,19 +2125,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2223,19 +2216,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2250,6 +2237,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2373,19 +2361,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2499,16 +2481,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRelPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2520,7 +2499,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3145,6 +3124,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RelPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3162,7 +3142,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3714,3 +3697,221 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRelPermissionInfo
+ *		Creates RelPermissionInfo for a given relation and adds it into the
+ *		provided list unless one with the same OID is found in it
+ *
+ * Returns the RelPermssionInfo and sets rte->perminfoindex if needed.
+ */
+RelPermissionInfo *
+AddRelPermissionInfo(List **relpermlist, RangeTblEntry *rte)
+{
+	Assert(rte->rtekind == RTE_RELATION);
+
+	Assert(rte->perminfoindex == 0);
+	return AddRelPermissionInfoInternal(relpermlist, rte->relid,
+										&rte->perminfoindex);
+}
+
+/*
+ * AddRelPermissionInfoInternal
+ *		Sub-routine of AddRelPermissionInfo that does the actual work
+ */
+static RelPermissionInfo *
+AddRelPermissionInfoInternal(List **relpermlist, Oid relid,
+							 Index *perminfoindex)
+{
+	RelPermissionInfo *perminfo;
+
+	/*
+	 * To prevent duplicate entries for a given relation, check if already in
+	 * the list.
+	 */
+	perminfo = GetRelPermissionInfoInternal(*relpermlist, relid, perminfoindex,
+											true);
+	if (perminfo)
+	{
+		Assert(*perminfoindex >= 0);
+		return perminfo;
+	}
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RelPermissionInfo);
+	perminfo->relid = relid;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*relpermlist = lappend(*relpermlist, perminfo);
+
+	/* Note its index.  */
+	*perminfoindex = list_length(*relpermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfo
+ *		Find RelPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RelPermissionInfo *
+GetRelPermissionInfo(List *relpermlist, RangeTblEntry *rte)
+{
+	RelPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(relpermlist))
+		elog(ERROR, "invalid perminfoindex in RTE with relid %u",
+			 rte->relid);
+	perminfo = GetRelPermissionInfoInternal(relpermlist, rte->relid,
+											&rte->perminfoindex, false);
+	if (rte->relid != perminfo->relid)
+		elog(ERROR, "permission info at index %u (with OID %u) does not match requested OID %u",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
+
+/*
+ * GetRelPermissionInfoInternal
+ *		Sub-routine of GetRelPermissionInfo that does the actual work
+ *
+ * If *perminfoindex is 0, the list is scanned to find one with given relid.
+ * If found, *perminfoindex is set to its 1-based index in the list.
+ *
+ * If *perminfoindex is already valid (> 0), it means that the caller expects
+ * to find the entry it's looking for at that location in the list.
+ */
+static RelPermissionInfo *
+GetRelPermissionInfoInternal(List *relpermlist, Oid relid,
+							 Index *perminfoindex,
+							 bool missing_ok)
+{
+	RelPermissionInfo *perminfo;
+
+	if (*perminfoindex == 0)
+	{
+		ListCell   *lc;
+
+		foreach(lc, relpermlist)
+		{
+			perminfo = (RelPermissionInfo *) lfirst(lc);
+			if (perminfo->relid == relid)
+			{
+				*perminfoindex = foreach_current_index(lc) + 1;
+				return perminfo;
+			}
+		}
+	}
+	else if (*perminfoindex > 0)
+	{
+
+		perminfo = (RelPermissionInfo *)
+			list_nth(relpermlist, *perminfoindex - 1);
+		Assert(perminfo != NULL && OidIsValid(perminfo->relid));
+
+		return perminfo;
+	}
+
+	if (!missing_ok)
+		elog(ERROR, "permission info of relation %u not found", relid);
+
+	return NULL;
+}
+
+/*
+ * GetRelPermissionInfoIndex
+ *		Returns a 1-based index of the RelPermissionInfo of matching relid if
+ *		found in the given list
+ *
+ * 0 indicates that one was not found.
+ */
+static Index
+GetRelPermissionInfoIndex(List *relpermlist, Oid relid)
+{
+	ListCell   *lc;
+
+	foreach(lc, relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(lc);
+
+		if (perminfo->relid == relid)
+			return foreach_current_index(lc) + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * MergeRelPermissionInfos
+ *		Adds the RelPermissionInfos found in a source query (src_relpermlist)
+ *		into the destination query's list (*dest_relpermlist), "merging"
+ *		properties of any that are present in both.
+ *
+ * Caller must subsequently call ReassignRangeTablePermInfoIndexes() on the
+ * source query's range table.
+ */
+void
+MergeRelPermissionInfos(List **dest_relpermlist, List *src_relpermlist)
+{
+	ListCell *l;
+
+	if (src_relpermlist == NIL)
+		return;
+
+	foreach(l, src_relpermlist)
+	{
+		RelPermissionInfo *src_perminfo = (RelPermissionInfo *) lfirst(l);
+		RelPermissionInfo *dest_perminfo;
+		Index		ignored = 0;
+
+		dest_perminfo = AddRelPermissionInfoInternal(dest_relpermlist,
+													 src_perminfo->relid,
+													 &ignored);
+
+		dest_perminfo->inh |= src_perminfo->inh;
+		dest_perminfo->requiredPerms |= src_perminfo->requiredPerms;
+		if (!OidIsValid(dest_perminfo->checkAsUser))
+			dest_perminfo->checkAsUser = src_perminfo->checkAsUser;
+		dest_perminfo->selectedCols = bms_union(dest_perminfo->selectedCols,
+												src_perminfo->selectedCols);
+		dest_perminfo->insertedCols = bms_union(dest_perminfo->insertedCols,
+												src_perminfo->insertedCols);
+		dest_perminfo->updatedCols = bms_union(dest_perminfo->updatedCols,
+											   src_perminfo->updatedCols);
+		dest_perminfo->extraUpdatedCols = bms_union(dest_perminfo->extraUpdatedCols,
+													src_perminfo->extraUpdatedCols);
+	}
+}
+
+/*
+ * ReassignRangeTablePermInfoIndexes
+ * 		Updates perminfoindex of the relation RTEs in rtable so that they reflect
+ * 		their respective RelPermissionInfo entry's current position in permlist.
+ *
+ * This is provided for the sites that use MergeRelPermissionInfos() to merge
+ * the RelPermissionInfo entries of two queries.
+ */
+void
+ReassignRangeTablePermInfoIndexes(List *rtable, List *relpermlist)
+{
+	ListCell   *l;
+
+	foreach(l, rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		/*
+		 * Only RELATIONs would have been assigned a RelPermissionInfo and that
+		 * too only those that are mentioned in the query (not inheritance
+		 * child relations that are added afterwards), so we also check that
+		 * the RTE's existing index is valid.
+		 */
+		if (rte->rtekind != RTE_RELATION && rte->perminfoindex > 0)
+			continue;
+		rte->perminfoindex = GetRelPermissionInfoIndex(relpermlist, rte->relid);
+	}
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 16a0fe59e2..fbe1172708 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RelPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1422,12 +1426,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
-	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * as simple Vars.  Note: this case leaves us with the relation's
+	 * selectedCols bitmap showing the whole row as needing select permission,
+	 * as well as the individual columns.  However, we can only get here for
+	 * weird notations like (table.*).*, so it's not worth trying to clean up
+	 * --- arguably, the permissions marking is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b57253463b..e90f2162ca 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1222,7 +1222,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3012,9 +3013,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3070,6 +3068,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->relpermlist = pstate->p_relpermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3112,8 +3111,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 5f8c541763..5a670761a4 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -491,6 +492,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRelPermissionInfo(&estate->es_relpermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1787,7 +1790,7 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
+	RelPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1835,7 +1838,7 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_relpermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1845,14 +1848,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index a3c1ba8a40..22c89c8eb2 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1129,7 +1129,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index a5a1fb887f..8baa394f1a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -31,6 +31,7 @@
 #include "commands/policy.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
@@ -785,14 +786,7 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ *		field to the given userid in all RelPermissionInfos of the query.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +813,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RelPermissionInfos for this query. */
+	foreach(l, qry->relpermlist)
+	{
+		RelPermissionInfo *perminfo = (RelPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..fca2a1eaf0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -394,25 +394,9 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
-	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
-	 *
-	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
-	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
 	 * NOTE: because planner will destructively alter rtable, we must ensure
 	 * that rule action's rtable is separate and shares no substructure with
@@ -421,6 +405,27 @@ rewriteRuleAction(Query *parsetree,
 	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
 									 sub_action->rtable);
 
+	/*
+	 * Merge permission info lists to ensure that all permissions are checked
+	 * correctly.
+	 *
+	 * If the rule is INSTEAD, then the original query won't be executed at
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
+	 *
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
+	 */
+	MergeRelPermissionInfos(&sub_action->relpermlist, parsetree->relpermlist);
+
+	/* Update the combined rtable to reassign their perminfoindexes. */
+	ReassignRangeTablePermInfoIndexes(sub_action->rtable,
+									  sub_action->relpermlist);
+
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
 	 * case we'd better mark the sub_action correctly.
@@ -1589,16 +1594,18 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 
 /*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * Record in target_perminfo->extraUpdatedCols the indexes of any generated
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
 
-	target_rte->extraUpdatedCols = NULL;
+	target_perminfo->extraUpdatedCols = NULL;
 
 	if (constr && constr->has_generated_stored)
 	{
@@ -1616,9 +1623,9 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
+				target_perminfo->extraUpdatedCols =
+					bms_add_member(target_perminfo->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RelPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRelPermissionInfo(qry->relpermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RelPermissionInfo *view_perminfo;
+	RelPermissionInfo *base_perminfo;
+	RelPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3174,6 +3150,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+	base_perminfo = GetRelPermissionInfo(viewquery->relpermlist, base_rte);
 	Assert(base_rte->rtekind == RTE_RELATION);
 
 	/*
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RelPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRelPermissionInfo(parsetree->relpermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRelPermissionInfo(&parsetree->relpermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RelPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRelPermissionInfo(parsetree->relpermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..d0a292d46c 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RelPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRelPermissionInfo(root->relpermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index d2aa8d0ca3..d501947912 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1560,6 +1561,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	int			clause_relid;
 	Oid			userid;
@@ -1607,10 +1609,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
 	{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 51b3fdc9a0..58d35318c1 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1374,8 +1374,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
-	RangeTblEntry *pkrte;
-	RangeTblEntry *fkrte;
+	RelPermissionInfo *pk_perminfo;
+	RelPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1393,32 +1393,26 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 *
 	 * XXX are there any other show-stopper conditions to check?
 	 */
-	pkrte = makeNode(RangeTblEntry);
-	pkrte->rtekind = RTE_RELATION;
-	pkrte->relid = RelationGetRelid(pk_rel);
-	pkrte->relkind = pk_rel->rd_rel->relkind;
-	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
-
-	fkrte = makeNode(RangeTblEntry);
-	fkrte->rtekind = RTE_RELATION;
-	fkrte->relid = RelationGetRelid(fk_rel);
-	fkrte->relkind = fk_rel->rd_rel->relkind;
-	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+	pk_perminfo = makeNode(RelPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RelPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
@@ -1428,9 +1422,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
 		((pk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		  !pg_class_ownercheck(pk_perminfo->relid, GetUserId())) ||
 		 (fk_rel->rd_rel->relrowsecurity &&
-		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		  !pg_class_ownercheck(fk_perminfo->relid, GetUserId()))))
 		return false;
 
 	/*----------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d35e5605de..7548c3530d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5170,7 +5170,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5222,7 +5222,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5301,7 +5301,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5349,7 +5349,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5410,6 +5410,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5418,7 +5419,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5487,7 +5488,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bdb771d278..fd53780908 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -846,8 +846,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RelPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 3df1c5a97c..af40f21496 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *relpermlist;	/* single element list of RelPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d68a6b9d28..eb812b3308 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,7 +80,7 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
+/* Hook for plugins to get control in ExecCheckPermissions() */
 typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
 extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *relpermlist,
+				 bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -602,6 +603,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..25ae278deb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_relpermlist;		/* List of RelPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 98fe1abaa2..970637f115 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -151,6 +151,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *relpermlist;	/* list of RTEPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -964,37 +966,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1050,11 +1021,17 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RelPermissionInfo belonging to
+	 * this RTE (same relid in both) in the query's list of RelPermissionInfos;
+	 * 0 in non-RELATION RTEs.  It's set when the RTE is passed to
+	 * AddRelPermissionInfo() right after its creation in the parser.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1174,14 +1151,61 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RelPermissionInfo
+ * 		Per-relation information for permission checking. Added to the query
+ * 		by the parser when populating the query range table and subsequently
+ * 		editorialized on by the rewriter and the planner.  There is an entry
+ * 		each for all RTE_RELATION entries present in the range table, though
+ * 		different RTEs for the same relation share the RelPermissionInfo, that
+ * 		is, there is only one RelPermissionInfo containing a given relation
+ * 		OID (relid).
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RelPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* OID of the relation */
+	bool		inh;			/* true if inheritance children may exist */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
 	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RelPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e2081db4ed..f5208706c3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RelPermissionInfos */
+	List	   *finalrelpermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index dca2a21e7a..6417b3cac2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -71,6 +71,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *relpermlist;	/* list of RelPermissionInfo nodes for
+								 * the RTE_RELATION entries in rtable */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -702,6 +705,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* copy of RelOptInfo.userid */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..4e64c0b0e5 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -181,6 +181,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_relpermlist;	/* list of RelPermissionInfo nodes for
+									 * the RTE_RELATION entries in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +236,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in relpermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +274,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RelPermissionInfo *p_perminfo;	/* The relation's permissions entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index de21c3c649..923add1176 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,13 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RelPermissionInfo *AddRelPermissionInfo(List **relpermlist,
+											   RangeTblEntry *rte);
+extern RelPermissionInfo *GetRelPermissionInfo(List *relpermlist,
+											   RangeTblEntry *rte);
+extern void MergeRelPermissionInfos(List **dest_relpermlist,
+									List *src_relpermlist);
+extern void ReassignRangeTablePermInfoIndexes(List *rtable,
+											  List *relpermlist);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..f21786da35 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,7 +24,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+extern void fill_extraUpdatedCols(RelPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
-- 
2.35.3



  [application/octet-stream] v16-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (123.0K, ../../CA+HiwqGKnrnzZQB8xwW-fLVUbURQmvY9rBSyJ6arR2YO7KPFVA@mail.gmail.com/3-v16-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 7ecdad0e84b3a5f159d9e68a32a0fd9068ce41fd Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v16 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RelPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add an RTE for view relations.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteHandler.c          |  34 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/jsonb_sqljson.out   |  62 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/sqljson.out         |   6 +-
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 28 files changed, 764 insertions(+), 843 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ade797159d..552ec5dda3 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2528,7 +2528,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2591,7 +2591,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6466,10 +6466,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6560,10 +6560,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b97b8b0435..2cbc53b42c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f0958f03a0..2519970914 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -339,78 +339,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -573,12 +501,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fca2a1eaf0..6225ccba30 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,27 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before modifying, store a copy of itself so as to serve as the entry
+	 * to be used by the executor to lock the view relation and for the
+	 * planner to be able to record the view relation OID in the PlannedStmt
+	 * that it produces for the query.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1859,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b10e1c4c0d..5b98ebf089 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2216,7 +2216,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2232,7 +2232,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2248,7 +2248,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2264,7 +2264,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2282,7 +2282,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3261,7 +3261,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 601047fa3d..36bed0ac1b 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1544,7 +1544,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1596,7 +1596,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1612,7 +1612,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1628,7 +1628,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2113,15 +2113,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d63f4f1cba..f0d5531db0 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index a828b1f6de..b34429d713 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2052,7 +2052,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2104,19 +2104,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out
index ef496110af..be770482c5 100644
--- a/src/test/regress/expected/jsonb_sqljson.out
+++ b/src/test/regress/expected/jsonb_sqljson.out
@@ -1161,37 +1161,37 @@ SELECT * FROM
 	);
 \sv jsonb_table_view
 CREATE OR REPLACE VIEW public.jsonb_table_view AS
- SELECT "json_table".id,
-    "json_table".id2,
-    "json_table"."int",
-    "json_table".text,
-    "json_table"."char(4)",
-    "json_table".bool,
-    "json_table"."numeric",
-    "json_table".domain,
-    "json_table".js,
-    "json_table".jb,
-    "json_table".jst,
-    "json_table".jsc,
-    "json_table".jsv,
-    "json_table".jsb,
-    "json_table".jsbq,
-    "json_table".aaa,
-    "json_table".aaa1,
-    "json_table".exists1,
-    "json_table".exists2,
-    "json_table".exists3,
-    "json_table".js2,
-    "json_table".jsb2w,
-    "json_table".jsb2q,
-    "json_table".ia,
-    "json_table".ta,
-    "json_table".jba,
-    "json_table".a1,
-    "json_table".b1,
-    "json_table".a11,
-    "json_table".a21,
-    "json_table".a22
+ SELECT id,
+    id2,
+    "int",
+    text,
+    "char(4)",
+    bool,
+    "numeric",
+    domain,
+    js,
+    jb,
+    jst,
+    jsc,
+    jsv,
+    jsb,
+    jsbq,
+    aaa,
+    aaa1,
+    exists1,
+    exists2,
+    exists3,
+    js2,
+    jsb2w,
+    jsb2q,
+    ia,
+    ta,
+    jba,
+    a1,
+    b1,
+    a11,
+    a21,
+    a22
    FROM JSON_TABLE(
             'null'::jsonb, '$[*]' AS json_table_path_1
             PASSING
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7ec3d2688f..fc35d8a567 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1453,10 +1453,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1702,23 +1702,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1732,10 +1732,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1803,13 +1803,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1822,57 +1822,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1895,8 +1895,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1905,13 +1905,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2021,16 +2021,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2068,26 +2068,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2106,41 +2106,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2150,68 +2150,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2228,19 +2228,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2250,19 +2250,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2306,64 +2306,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2548,24 +2548,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3085,7 +3085,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3124,8 +3124,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3137,8 +3137,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3150,8 +3150,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3163,8 +3163,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index bdd0969a50..6b9cddc829 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1059,7 +1059,7 @@ SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING t
 FROM generate_series(1,5) i;
 \sv json_objectagg_view
 CREATE OR REPLACE VIEW public.json_objectagg_view AS
- SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg"
+ SELECT JSON_OBJECTAGG(i : ('111'::text || i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i > 3) AS "json_objectagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_objectagg_view;
 -- Test JSON_ARRAYAGG deparsing
@@ -1095,7 +1095,7 @@ SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text
 FROM generate_series(1,5) i;
 \sv json_arrayagg_view
 CREATE OR REPLACE VIEW public.json_arrayagg_view AS
- SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg"
+ SELECT JSON_ARRAYAGG(('111'::text || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) AS "json_arrayagg"
    FROM generate_series(1, 5) i(i)
 DROP VIEW json_arrayagg_view;
 -- Test JSON_ARRAY(subquery) deparsing
@@ -1313,7 +1313,7 @@ SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT
 \sv is_json_view
 CREATE OR REPLACE VIEW public.is_json_view AS
  SELECT '1'::text IS JSON AS "any",
-    ('1'::text || i.i) IS JSON SCALAR AS scalar,
+    ('1'::text || i) IS JSON SCALAR AS scalar,
     NOT '[]'::text IS JSON ARRAY AS "array",
     '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
    FROM generate_series(1, 3) i(i)
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 89a34ffbb2..f2b2156d2b 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 433a0bb025..7a9968afe2 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 30dd900e11..adbe32c32e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 55ac49be26..73a3a52562 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 493c6186e1..b524e1665f 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-07-27 21:04  Tom Lane <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Tom Lane @ 2022-07-27 21:04 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

Amit Langote <[email protected]> writes:
> [ v16 patches ]

I took a quick look at this ...

I think that the notion behind MergeRelPermissionInfos, ie that
a single RelPermissionInfo can represent *all* the checks for
a given table OID, is fundamentally wrong.  For example, when
merging a view into an outer query that references a table
also used by the view, the checkAsUser fields might be different,
and the permissions to check might be different, and the columns
those permissions need to hold for might be different.  Blindly
bms_union'ing the column sets will lead to requiring far more
permissions than the query should require.  Conversely, this
approach could lead to allowing cases we should reject, if you
happen to "merge" checkAsUser in a way that ends in checking as a
higher-privilege user than should be checked.

I'm inclined to think that you should abandon the idea of
merging RelPermissionInfos at all.  It can only buy us much
in the case of self-joins, which ought to be rare.  It'd
be better to just say "there is one RelPermissionInfo for
each RTE requiring any sort of permissions check".  Either
that or you need to complicate RelPermissionInfo a lot, but
I don't see the advantage.

It'd likely be better to rename ExecutorCheckPerms_hook,
say to ExecCheckPermissions_hook given the rename of
ExecCheckRTPerms.  As it stands, it *looks* like the API
of that hook has not changed, when it has.  Better to
break calling code visibly than to make people debug their
way to an understanding that the List contents are no longer
what they expected.  A different idea could be to pass both
the rangetable and relpermlist, again making the API break obvious
(and who's to say a hook might not still want the rangetable?)

In parsenodes.h:
+    List       *relpermlist;    /* list of RTEPermissionInfo nodes for
+                                 * the RTE_RELATION entries in rtable */

I find this comment not very future-proof, if indeed it's strictly
correct even today.  Maybe better "list of RelPermissionInfo nodes for
rangetable entries having perminfoindex > 0".  Likewise for the comment
in RangeTableEntry: there's no compelling reason to assume that all and
only RELATION RTEs will have RelPermissionInfo.  Even if that remains
true at parse time it's falsified during planning.

Also note typo in node name: that comment is the only reference to
"RTEPermissionInfo" AFAICS.  Although, given the redefinition I
suggest above, arguably "RTEPermissionInfo" is the better name?

I'm confused as to why RelPermissionInfo.inh exists.  It doesn't
seem to me that permissions checking should care about child rels.

Why did you add checkAsUser to ForeignScan (and not any other scan
plan nodes)?  At best that's pretty asymmetric, but it seems mighty
bogus: under what circumstances would an FDW need to know that but
not any of the other RelPermissionInfo fields?  This seems to
indicate that someplace we should work harder at making the
RelPermissionInfo list available to FDWs.  (CustomScan providers
might have similar issues, btw.)

I've not looked at much of the actual code, just the .h file changes.
Haven't studied 0002 either.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-07-27 21:18  Tom Lane <[email protected]>
  parent: Tom Lane <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Tom Lane @ 2022-07-27 21:18 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

... One more thing: maybe we should rethink where to put
extraUpdatedCols.  Between the facts that it's not used for
actual permissions checks, and that it's calculated by the
rewriter not parser, it doesn't seem like it really belongs
in RelPermissionInfo.  Should we keep it in RangeTblEntry?
Should it go somewhere else entirely?  I'm just speculating,
but now is a good time to think about it.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-09-07 09:23  Amit Langote <[email protected]>
  parent: Tom Lane <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-09-07 09:23 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Jul 28, 2022 at 6:04 AM Tom Lane <[email protected]> wrote:
> Amit Langote <[email protected]> writes:
> > [ v16 patches ]
>
> I took a quick look at this ...

Thanks for the review and sorry about the delay.

> I think that the notion behind MergeRelPermissionInfos, ie that
> a single RelPermissionInfo can represent *all* the checks for
> a given table OID, is fundamentally wrong.  For example, when
> merging a view into an outer query that references a table
> also used by the view, the checkAsUser fields might be different,
> and the permissions to check might be different, and the columns
> those permissions need to hold for might be different.  Blindly
> bms_union'ing the column sets will lead to requiring far more
> permissions than the query should require.  Conversely, this
> approach could lead to allowing cases we should reject, if you
> happen to "merge" checkAsUser in a way that ends in checking as a
> higher-privilege user than should be checked.
>
> I'm inclined to think that you should abandon the idea of
> merging RelPermissionInfos at all.  It can only buy us much
> in the case of self-joins, which ought to be rare.  It'd
> be better to just say "there is one RelPermissionInfo for
> each RTE requiring any sort of permissions check".  Either
> that or you need to complicate RelPermissionInfo a lot, but
> I don't see the advantage.

OK, I agree that the complexity of sharing a RelPermissionInfo between
RTEs far exceeds any performance benefit to be had from it.

I have changed things so that there's one RelPermissionInfo for every
RTE_RELATION entry in the range table, except those that the planner
adds when expanding inheritance.

> It'd likely be better to rename ExecutorCheckPerms_hook,
> say to ExecCheckPermissions_hook given the rename of
> ExecCheckRTPerms.  As it stands, it *looks* like the API
> of that hook has not changed, when it has.  Better to
> break calling code visibly than to make people debug their
> way to an understanding that the List contents are no longer
> what they expected.  A different idea could be to pass both
> the rangetable and relpermlist, again making the API break obvious
> (and who's to say a hook might not still want the rangetable?)

I agree it'd be better to break the API more explicitly.  Actually, I
decided to adopt both of these suggestions: renamed the hook and kept
the rangeTable parameter.

> In parsenodes.h:
> +    List       *relpermlist;    /* list of RTEPermissionInfo nodes for
> +                                 * the RTE_RELATION entries in rtable */
>
> I find this comment not very future-proof, if indeed it's strictly
> correct even today.  Maybe better "list of RelPermissionInfo nodes for
> rangetable entries having perminfoindex > 0".  Likewise for the comment
> in RangeTableEntry: there's no compelling reason to assume that all and
> only RELATION RTEs will have RelPermissionInfo.  Even if that remains
> true at parse time it's falsified during planning.

Ah right, inheritance children's RTE_RELATION entries don't have one.
I've fixed the comment.

> Also note typo in node name: that comment is the only reference to
> "RTEPermissionInfo" AFAICS.  Although, given the redefinition I
> suggest above, arguably "RTEPermissionInfo" is the better name?

Agreed.  I've renamed RelPermissionInfo to RTEPermissionInfo and
relpermlist to rtepermlist.

> I'm confused as to why RelPermissionInfo.inh exists.  It doesn't
> seem to me that permissions checking should care about child rels.

I had to do this for contrib/sepgsql, sepgsql_dml_privileges() has this:

        /*
         * If this RangeTblEntry is also supposed to reference inherited
         * tables, we need to check security label of the child tables. So, we
         * expand rte->relid into list of OIDs of inheritance hierarchy, then
         * checker routine will be invoked for each relations.
         */
        if (!rte->inh)
            tableIds = list_make1_oid(rte->relid);
        else
            tableIds = find_all_inheritors(rte->relid, NoLock, NULL);

> Why did you add checkAsUser to ForeignScan (and not any other scan
> plan nodes)?  At best that's pretty asymmetric, but it seems mighty
> bogus: under what circumstances would an FDW need to know that but
> not any of the other RelPermissionInfo fields?  This seems to
> indicate that someplace we should work harder at making the
> RelPermissionInfo list available to FDWs.  (CustomScan providers
> might have similar issues, btw.)

I think I had tried doing what you are suggesting -- getting the
checkAsUser from a RelPermissionInfo rather than putting that in
ForeignScan -- though we can't do it, because we need the userid for
child foreign table relations, for which we don't create a
RelPermissionInfo.  ForeignScan nodes for child relations don't store
their root parent's RT index, so we can't get the checkAsUser using
the root parent's RelPermissionInfo, like I could do for child foreign
table "result" relations using ResultRelInfo.ri_RootResultRelInfo.

As to why an FDW may not need to know any of the other
RelPermissionInfo fields, IIUC, ExecCheckPermissions() would have done
everything that ought to be done *locally* using that information.
Whatever the remote side needs to know wrt access permission checking
should have been put in fdw_private, no?

On Thu, Jul 28, 2022 at 6:18 AM Tom Lane <[email protected]> wrote:
> ... One more thing: maybe we should rethink where to put
> extraUpdatedCols.  Between the facts that it's not used for
> actual permissions checks, and that it's calculated by the
> rewriter not parser, it doesn't seem like it really belongs
> in RelPermissionInfo.  Should we keep it in RangeTblEntry?
> Should it go somewhere else entirely?  I'm just speculating,
> but now is a good time to think about it.

Indeed, extraUpdatedCols doesn't really seem to belong in
RelPermissionInfo, so I have left it in RangeTblEntry.

Attached updated patches.
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v17-0001-Rework-query-relation-permission-checking.patch (145.6K, ../../CA+HiwqEz4=cjQAYNu1_KU0uOJzaTbunTPjypFCMGNuSYTgCRuA@mail.gmail.com/2-v17-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 38dea7ad84fd983e17605f131c128294e8049774 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v17 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   7 +-
 src/backend/nodes/readfuncs.c                 |   7 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 155 +++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 951 insertions(+), 565 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 16320170ce..7a8521d03f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int len,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1935,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1943,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1967,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1979,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2147,6 +2183,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2224,6 +2261,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2240,7 +2279,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2626,7 +2666,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2646,13 +2685,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3955,12 +3993,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3972,12 +4010,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index e8bb168aea..6090769a68 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3c6e09815e..16ef4cab3a 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1287,7 +1287,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dacc989d85..b9fd58d349 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12291,7 +12296,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18039,7 +18045,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18966,7 +18973,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..5eed90c722 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f1fd7f7e8b..05ff93e3c6 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 901dd435ef..b992dbc657 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 60610e3a4b..c8f52ae11d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -475,6 +475,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -528,12 +529,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bee62fc15c..2d22d110d7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -302,6 +302,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_INT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -365,12 +366,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index cd8a3ef7cb..5b1416c9fa 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 079bd0bfdf..c75fb92eb8 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6208,6 +6212,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6335,6 +6340,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..10c2aa13f6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,83 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+	else
+		updatedCols = perminfo->updatedCols;
+
+	/* extraUpdatedCols can be obtained directly from the RTE. */
+	rte = planner_rt_fetch(rel->relid, root);
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 061d0bcc50..6e301fccbe 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index f44937a8bb..f8a6ccef8a 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4e1593d900..e0a06d55f8 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1141,7 +1141,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1376,6 +1376,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1390,7 +1391,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1423,11 +1427,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6d283006e3..66bba6e72f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 5f8c541763..491f5aa9c6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -491,6 +492,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1788,6 +1791,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1836,6 +1840,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1845,14 +1850,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 62e0ffecd8..ad3f4cb15e 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1129,7 +1129,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..f9b8dc3e6f 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ee05e230e0..c8a5d5c4b9 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 51b3fdc9a0..86b7aeb6f5 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 50b588e3d0..2e3d8df526 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5170,7 +5170,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5222,7 +5222,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5301,7 +5301,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5349,7 +5349,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5410,6 +5410,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5418,7 +5419,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5487,7 +5488,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 82925b4b63..72634171f0 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6958306a7d..14231ce0a8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -151,6 +151,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -964,37 +967,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1050,11 +1022,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1174,14 +1151,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 294cfe9c47..478353e26f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index dca2a21e7a..a981c0bbf4 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -71,6 +71,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -702,6 +706,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index de21c3c649..11bd11f40d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index 98b9b3a288..607405009c 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v17-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqEz4=cjQAYNu1_KU0uOJzaTbunTPjypFCMGNuSYTgCRuA@mail.gmail.com/3-v17-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 444b8ea4b1015a3f02fdfb3d4f6ead2a157f2c3b Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v17 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7bf35602b0..76b4668779 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2528,7 +2528,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2591,7 +2591,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6474,10 +6474,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6591,10 +6591,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b97b8b0435..2cbc53b42c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index f9b8dc3e6f..0fe1090cd8 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 2873b662fb..a068a5a873 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3270,7 +3270,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index b2198724e3..4a55e42dd2 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1622,7 +1622,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1674,7 +1674,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1690,7 +1690,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1706,7 +1706,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2191,15 +2191,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d63f4f1cba..f0d5531db0 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index a828b1f6de..b34429d713 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2052,7 +2052,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2104,19 +2104,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8fe42ec560..b8cbc0cf0f 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 55dcd668c9..ddaefdd91d 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 30dd900e11..adbe32c32e 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-02 17:10  Andres Freund <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Andres Freund @ 2022-10-02 17:10 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; Justin Pryzby <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On 2022-09-07 18:23:06 +0900, Amit Langote wrote:
> Attached updated patches.

Thanks to Justin's recent patch (89d16b63527) to add
-DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST
to the FreeBSD ci task we now see the following:

https://cirrus-ci.com/task/4772259058417664
https://api.cirrus-ci.com/v1/artifact/task/4772259058417664/testrun/build/testrun/main/regress/regre...

diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/updatable_views.out /tmp/cirrus-ci-build/build/testrun/main/regress/results/updatable_views.out
--- /tmp/cirrus-ci-build/src/test/regress/expected/updatable_views.out	2022-10-02 10:37:08.888945000 +0000
+++ /tmp/cirrus-ci-build/build/testrun/main/regress/results/updatable_views.out	2022-10-02 10:40:26.947887000 +0000
@@ -1727,14 +1727,16 @@
 (4 rows)

 UPDATE base_tbl SET id = 2000 WHERE id = 2;
+WARNING:  outfuncs/readfuncs failed to produce an equal rewritten parse tree
 UPDATE rw_view1 SET id = 3000 WHERE id = 3;
+WARNING:  outfuncs/readfuncs failed to produce an equal rewritten parse tree
 SELECT * FROM base_tbl;
   id  | idplus1
 ------+---------
     1 |       2
     4 |       5
- 2000 |    2001
- 3000 |    3001
+ 2000 |       3
+ 3000 |       4
 (4 rows)

 DROP TABLE base_tbl CASCADE;

and many more.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-03 09:10  Amit Langote <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-10-03 09:10 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On Mon, Oct 3, 2022 at 2:10 AM Andres Freund <[email protected]> wrote:
> On 2022-09-07 18:23:06 +0900, Amit Langote wrote:
> > Attached updated patches.
>
> Thanks to Justin's recent patch (89d16b63527) to add
> -DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST
> to the FreeBSD ci task we now see the following:
>
> https://cirrus-ci.com/task/4772259058417664
> https://api.cirrus-ci.com/v1/artifact/task/4772259058417664/testrun/build/testrun/main/regress/regre...
>
> diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/updatable_views.out /tmp/cirrus-ci-build/build/testrun/main/regress/results/updatable_views.out
> --- /tmp/cirrus-ci-build/src/test/regress/expected/updatable_views.out  2022-10-02 10:37:08.888945000 +0000
> +++ /tmp/cirrus-ci-build/build/testrun/main/regress/results/updatable_views.out 2022-10-02 10:40:26.947887000 +0000
> @@ -1727,14 +1727,16 @@
>  (4 rows)
>
>  UPDATE base_tbl SET id = 2000 WHERE id = 2;
> +WARNING:  outfuncs/readfuncs failed to produce an equal rewritten parse tree
>  UPDATE rw_view1 SET id = 3000 WHERE id = 3;
> +WARNING:  outfuncs/readfuncs failed to produce an equal rewritten parse tree
>  SELECT * FROM base_tbl;
>    id  | idplus1
>  ------+---------
>      1 |       2
>      4 |       5
> - 2000 |    2001
> - 3000 |    3001
> + 2000 |       3
> + 3000 |       4
>  (4 rows)
>
>  DROP TABLE base_tbl CASCADE;
>
> and many more.

Thanks for the heads up.  Grateful for those new -D flags.

Turns out I had forgotten to update out/readRangeTblEntry() after
bringing extraUpdatedCols back into RangeTblEntry per Tom's comment.

Fixed in the attached.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v18-0001-Rework-query-relation-permission-checking.patch (145.6K, ../../CA+HiwqHvJP-rmt9Q1H7H0RsNWr-GxrLh6qEp1PV2epHvWTbDAA@mail.gmail.com/2-v18-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 8df39257774986b761209545ef9fba30ed0a7182 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v18 1/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 155 +++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 951 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index dd858aba03..9f233c37c4 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1935,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1943,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1967,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1979,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2147,6 +2183,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2224,6 +2261,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2240,7 +2279,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2626,7 +2666,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2646,13 +2685,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3955,12 +3993,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3972,12 +4010,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 175aa837f2..575ac5c81d 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7d8a75d23c..4e617892d8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12303,7 +12308,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18024,7 +18030,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18951,7 +18958,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8014d1fd25..d03fcdf2fd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6209,6 +6213,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6336,6 +6341,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..10c2aa13f6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,83 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+	else
+		updatedCols = perminfo->updatedCols;
+
+	/* extraUpdatedCols can be obtained directly from the RTE. */
+	rte = planner_rt_fetch(rel->relid, root);
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 202a38f813..77648263f7 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 207a5805ba..e834130151 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1789,6 +1792,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1837,6 +1841,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1846,14 +1851,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d02fd83c0a..7dbbbc629b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1808388397..14a54eae53 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5171,7 +5171,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5223,7 +5223,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5302,7 +5302,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5350,7 +5350,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5411,6 +5411,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5419,7 +5420,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5488,7 +5489,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 294cfe9c47..478353e26f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v18-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqHvJP-rmt9Q1H7H0RsNWr-GxrLh6qEp1PV2epHvWTbDAA@mail.gmail.com/3-v18-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From b9416f7bc74e0fbb80e1644b1ccc44bf968c3746 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v18 2/2] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2e4e82a94f..cfe37815f0 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6552,10 +6552,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6669,10 +6669,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7dbbbc629b..156c033bda 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index b2198724e3..4a55e42dd2 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1622,7 +1622,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1674,7 +1674,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1690,7 +1690,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1706,7 +1706,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2191,15 +2191,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 55dcd668c9..ddaefdd91d 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-04 03:45  Amit Langote <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-04 03:45 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Jul 28, 2022 at 6:18 AM Tom Lane <[email protected]> wrote:
> ... One more thing: maybe we should rethink where to put
> extraUpdatedCols.  Between the facts that it's not used for
> actual permissions checks, and that it's calculated by the
> rewriter not parser, it doesn't seem like it really belongs
> in RelPermissionInfo.  Should we keep it in RangeTblEntry?
> Should it go somewhere else entirely?  I'm just speculating,
> but now is a good time to think about it.

After fixing the issue related to this mentioned by Andres, I started
thinking more about this, especially the "Should it go somewhere else
entirely?" part.

I've kept extraUpdatedCols in RangeTblEntry in the latest patch, but
perhaps it makes sense to put that into Query?  So, the stanza in
RewriteQuery() that sets extraUpdatedCols will be changed as:

@@ -3769,7 +3769,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
                                    NULL, 0, NULL);

            /* Also populate extraUpdatedCols (for generated columns) */
-           fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
+           parsetree->extraUpdatedCols =
+               get_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
        }
        else if (event == CMD_MERGE)
        {

Then, like withCheckOptionLists et al, have grouping_planner()
populate an extraUpdatedColsBitmaps in ModifyTable.
ExecInitModifyTable() will assign them directly into ResultRelInfos.
That way, anyplace in the executor that needs to look at
extraUpdatedCols of a given result relation can get that from its
ResultRelInfo, rather than from the RangeTblEntry as now.

Thoughts?


--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-04 03:54  Tom Lane <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Tom Lane @ 2022-10-04 03:54 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

Amit Langote <[email protected]> writes:
> On Thu, Jul 28, 2022 at 6:18 AM Tom Lane <[email protected]> wrote:
>> ... One more thing: maybe we should rethink where to put
>> extraUpdatedCols.  Between the facts that it's not used for
>> actual permissions checks, and that it's calculated by the
>> rewriter not parser, it doesn't seem like it really belongs
>> in RelPermissionInfo.  Should we keep it in RangeTblEntry?
>> Should it go somewhere else entirely?  I'm just speculating,
>> but now is a good time to think about it.

> I've kept extraUpdatedCols in RangeTblEntry in the latest patch, but
> perhaps it makes sense to put that into Query?

That's got morally the same problem as keeping it in RangeTblEntry:
those are structures that are built by the parser.  Hacking on them
later isn't terribly clean.

I wonder if it could make sense to postpone calculation of the
extraUpdatedCols out of the rewriter and into the planner, with
the idea that it ends up $someplace in the finished plan tree
but isn't part of the original parsetree.

A different aspect of this is that putting it in Query doesn't
make a lot of sense unless there is only one version of the
bitmap per Query.  In simple UPDATEs that would be true, but
I think that inherited/partitioned UPDATEs would need one per
result relation, which is likely the reason it got dumped in
RangeTblEntry to begin with.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-04 04:11  Amit Langote <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-04 04:11 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Oct 4, 2022 at 12:54 PM Tom Lane <[email protected]> wrote:
> Amit Langote <[email protected]> writes:
> > On Thu, Jul 28, 2022 at 6:18 AM Tom Lane <[email protected]> wrote:
> >> ... One more thing: maybe we should rethink where to put
> >> extraUpdatedCols.  Between the facts that it's not used for
> >> actual permissions checks, and that it's calculated by the
> >> rewriter not parser, it doesn't seem like it really belongs
> >> in RelPermissionInfo.  Should we keep it in RangeTblEntry?
> >> Should it go somewhere else entirely?  I'm just speculating,
> >> but now is a good time to think about it.
>
> > I've kept extraUpdatedCols in RangeTblEntry in the latest patch, but
> > perhaps it makes sense to put that into Query?
>
> That's got morally the same problem as keeping it in RangeTblEntry:
> those are structures that are built by the parser.  Hacking on them
> later isn't terribly clean.
>
> I wonder if it could make sense to postpone calculation of the
> extraUpdatedCols out of the rewriter and into the planner, with
> the idea that it ends up $someplace in the finished plan tree
> but isn't part of the original parsetree.

Looking at PlannerInfo.update_colnos, something that's needed for
execution but not in Query, maybe we can make preprocess_targetlist()
also populate an PlannerInfo.extraUpdatedCols?

> A different aspect of this is that putting it in Query doesn't
> make a lot of sense unless there is only one version of the
> bitmap per Query.  In simple UPDATEs that would be true, but
> I think that inherited/partitioned UPDATEs would need one per
> result relation, which is likely the reason it got dumped in
> RangeTblEntry to begin with.

Yeah, so if we have PlannerInfos.extraUpdatedCols as the root table's
version of that, grouping_planner() can make copies for all result
relations and put the list in ModifyTable.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-06 13:29  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 3 replies; 73+ messages in thread

From: Amit Langote @ 2022-10-06 13:29 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Oct 4, 2022 at 1:11 PM Amit Langote <[email protected]> wrote:
> On Tue, Oct 4, 2022 at 12:54 PM Tom Lane <[email protected]> wrote:
> > Amit Langote <[email protected]> writes:
> > > On Thu, Jul 28, 2022 at 6:18 AM Tom Lane <[email protected]> wrote:
> > >> ... One more thing: maybe we should rethink where to put
> > >> extraUpdatedCols.  Between the facts that it's not used for
> > >> actual permissions checks, and that it's calculated by the
> > >> rewriter not parser, it doesn't seem like it really belongs
> > >> in RelPermissionInfo.  Should we keep it in RangeTblEntry?
> > >> Should it go somewhere else entirely?  I'm just speculating,
> > >> but now is a good time to think about it.
> >
> > > I've kept extraUpdatedCols in RangeTblEntry in the latest patch, but
> > > perhaps it makes sense to put that into Query?
> >
> > That's got morally the same problem as keeping it in RangeTblEntry:
> > those are structures that are built by the parser.  Hacking on them
> > later isn't terribly clean.
> >
> > I wonder if it could make sense to postpone calculation of the
> > extraUpdatedCols out of the rewriter and into the planner, with
> > the idea that it ends up $someplace in the finished plan tree
> > but isn't part of the original parsetree.
>
> Looking at PlannerInfo.update_colnos, something that's needed for
> execution but not in Query, maybe we can make preprocess_targetlist()
> also populate an PlannerInfo.extraUpdatedCols?
>
> > A different aspect of this is that putting it in Query doesn't
> > make a lot of sense unless there is only one version of the
> > bitmap per Query.  In simple UPDATEs that would be true, but
> > I think that inherited/partitioned UPDATEs would need one per
> > result relation, which is likely the reason it got dumped in
> > RangeTblEntry to begin with.
>
> Yeah, so if we have PlannerInfos.extraUpdatedCols as the root table's
> version of that, grouping_planner() can make copies for all result
> relations and put the list in ModifyTable.

I tried in the attached 0004.  ModifyTable gets a new member
extraUpdatedColsBitmaps, which is List of Bitmapset "nodes".

Actually, List of Bitmapsets turned out to be something that doesn't
just-work with our Node infrastructure, which I found out thanks to
-DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
first-class support for copy/equal/write/read support for Bitmapsets,
such that writeNode() can write appropriately labeled versions of them
and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
didn't actually go ahead and make *all* Bitmapsets in the plan trees
to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
just use *_NODE_FIELD().

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v19-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqGRsCOUk7XmN6dnCRnNz8UMfcY0BGno-Dn=5fXQ2xjQBw@mail.gmail.com/2-v19-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From e9925de0df0a5803c75cfc4b081d93404708d03d Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v19 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index cc9e39c4a5..b6c3749395 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7dbbbc629b..156c033bda 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 55dcd668c9..ddaefdd91d 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



  [application/octet-stream] v19-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (4.6K, ../../CA+HiwqGRsCOUk7XmN6dnCRnNz8UMfcY0BGno-Dn=5fXQ2xjQBw@mail.gmail.com/3-v19-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From 16a776bf9a4106acbd7b614e54a60d062eb132f3 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v19 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 7 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
-- 
2.35.3



  [application/octet-stream] v19-0001-Rework-query-relation-permission-checking.patch (145.6K, ../../CA+HiwqGRsCOUk7XmN6dnCRnNz8UMfcY0BGno-Dn=5fXQ2xjQBw@mail.gmail.com/4-v19-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 616768f11da3b92b72645bcd399aa8cb64e9c1da Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v19 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 155 +++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 951 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index dd858aba03..9f233c37c4 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1935,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1943,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1967,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1979,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2147,6 +2183,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2224,6 +2261,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2240,7 +2279,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2626,7 +2666,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2646,13 +2685,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3955,12 +3993,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3972,12 +4010,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 175aa837f2..575ac5c81d 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1f774ac065..c2f50ae215 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12303,7 +12308,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18024,7 +18030,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18951,7 +18958,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d0fd6e072..9576b69f1a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6210,6 +6214,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6337,6 +6342,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..10c2aa13f6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,83 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+	else
+		updatedCols = perminfo->updatedCols;
+
+	/* extraUpdatedCols can be obtained directly from the RTE. */
+	rte = planner_rt_fetch(rel->relid, root);
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c2b5474f5f..95cfe7d2b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 207a5805ba..e834130151 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1789,6 +1792,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1837,6 +1841,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1846,14 +1851,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d02fd83c0a..7dbbbc629b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 234fb66580..e1065db5c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5139,7 +5139,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5191,7 +5191,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5270,7 +5270,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5318,7 +5318,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5379,6 +5379,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5387,7 +5388,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5456,7 +5457,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6bda383bea..99c8d4f611 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v19-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (27.7K, ../../CA+HiwqGRsCOUk7XmN6dnCRnNz8UMfcY0BGno-Dn=5fXQ2xjQBw@mail.gmail.com/5-v19-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 4fe45ab83aa1aa406e67dd1ef48af98d8e5b8afc Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v19 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++--
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 17 +++++++
 src/backend/optimizer/prep/preptlist.c   | 49 ++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 64 +++++++++++++++---------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 -----------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 138 insertions(+), 88 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 04454ad6e6..e034e3ceee 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..bcd380516d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -532,7 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f854855951..5c1c7aed2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9576b69f1a..8913200879 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,24 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, resultRelation,
+															   extraUpdatedCols,
+															   this_result_rel->top_parent_relids);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+														  extraUpdatedCols);
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1879,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1896,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1938,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..10a948ed40 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,43 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in target_perminfo->updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	/* These go into ModifyTable.extraUpdatedColsBitmaps, so make as Nodes. */
+	Bitmapset *extraUpdatedCols = makeNode(Bitmapset);
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 10c2aa13f6..ea632a94a6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -656,7 +655,8 @@ static Bitmapset *
 translate_col_privs(const Bitmapset *parent_privs,
 					List *translated_vars)
 {
-	Bitmapset  *child_privs = NULL;
+	/* These go into ModifyTable.extraUpdatedColsBitmaps, so make as Nodes. */
+	Bitmapset  *child_privs = makeNode(Bitmapset);
 	bool		whole_row;
 	int			attno;
 	ListCell   *lc;
@@ -866,13 +866,16 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
-translate_col_privs_recurse(PlannerInfo *root, Index relid,
-							Bitmapset *top_parent_cols,
-							Relids top_parent_relids)
+Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, Index relid,
+							   Bitmapset *top_parent_cols,
+							   Relids top_parent_relids)
 {
 	AppendRelInfo *appinfo;
 
+	if (top_parent_cols == NULL)
+		return NULL;
+
 	Assert(root->append_rel_array != NULL);
 	appinfo = root->append_rel_array[relid];
 	Assert(appinfo != NULL);
@@ -883,9 +886,9 @@ translate_col_privs_recurse(PlannerInfo *root, Index relid,
 	 * and child relation pairs.
 	 */
 	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
-		translate_col_privs_recurse(root, appinfo->parent_relid,
-									top_parent_cols,
-									top_parent_relids);
+		translate_col_privs_multilevel(root, appinfo->parent_relid,
+									   top_parent_cols,
+									   top_parent_relids);
 
 	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
 }
@@ -903,6 +906,8 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 	Bitmapset *updatedCols,
 			  *extraUpdatedCols;
 
+	Assert(root->parse->commandType == CMD_UPDATE);
+
 	if (!IS_SIMPLE_REL(rel))
 		return NULL;
 
@@ -918,24 +923,33 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 	 * translate the columns found therein to match the given relation.
 	 */
 	if (rel->top_parent_relids != NULL)
+	{
 		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
 								root);
+	}
 	else
+	{
+		Assert(rel->relid == root->parse->resultRelation);
 		rte = planner_rt_fetch(rel->relid, root);
+	}
 
 	Assert(rte->perminfoindex > 0);
 	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
 
 	if (rel->top_parent_relids != NULL)
-		updatedCols = translate_col_privs_recurse(root, rel->relid,
-												  perminfo->updatedCols,
-												  rel->top_parent_relids);
+	{
+		updatedCols = translate_col_privs_multilevel(root, rel->relid,
+													 perminfo->updatedCols,
+													 rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel->relid,
+														  root->extraUpdatedCols,
+														  rel->top_parent_relids);
+	}
 	else
+	{
 		updatedCols = perminfo->updatedCols;
-
-	/* extraUpdatedCols can be obtained directly from the RTE. */
-	rte = planner_rt_fetch(rel->relid, root);
-	extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
+	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 70f61ae7b1..f70fe2736c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3643,6 +3643,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3658,6 +3660,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3722,6 +3725,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e834130151..b75455382e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1791,7 +1792,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1840,7 +1840,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1858,7 +1857,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 156c033bda..d62d457fc0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1592,46 +1592,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3670,7 +3630,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3682,7 +3641,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3767,9 +3725,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 080680ecd0..118a150d3c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1152,7 +1152,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 99c8d4f611..04c7403897 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaff24256e..2f8c9f65cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..3cc54ef7e3 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, Index relid,
+							   Bitmapset *top_parent_cols,
+							   Relids top_parent_relids);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-07 01:04  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  2 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-07 01:04 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> Actually, List of Bitmapsets turned out to be something that doesn't
> just-work with our Node infrastructure, which I found out thanks to
> -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> first-class support for copy/equal/write/read support for Bitmapsets,
> such that writeNode() can write appropriately labeled versions of them
> and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> didn't actually go ahead and make *all* Bitmapsets in the plan trees
> to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> just use *_NODE_FIELD().

All meson builds on the cfbot machines seem to have failed, maybe
because I didn't update src/include/nodes/meson.build to add
'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
an updated version assuming that's the problem.  (Will set up meson
builds on my machine to avoid this in the future.)

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v20-0001-Rework-query-relation-permission-checking.patch (145.6K, ../../CA+HiwqGFCcapwkC4eYLJzaAQbonoYW4+JaVjxis+bhxTL6Aw=A@mail.gmail.com/2-v20-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 616768f11da3b92b72645bcd399aa8cb64e9c1da Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v20 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 155 +++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 951 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index dd858aba03..9f233c37c4 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1512,16 +1514,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1813,7 +1814,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1892,6 +1894,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1903,6 +1935,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1910,6 +1943,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1933,6 +1967,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1944,7 +1979,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2147,6 +2183,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2224,6 +2261,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2240,7 +2279,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2626,7 +2666,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2646,13 +2685,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3955,12 +3993,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3972,12 +4010,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 175aa837f2..575ac5c81d 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1f774ac065..c2f50ae215 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12303,7 +12308,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18024,7 +18030,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18951,7 +18958,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d0fd6e072..9576b69f1a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6210,6 +6214,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6337,6 +6342,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..10c2aa13f6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,83 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+	else
+		updatedCols = perminfo->updatedCols;
+
+	/* extraUpdatedCols can be obtained directly from the RTE. */
+	rte = planner_rt_fetch(rel->relid, root);
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c2b5474f5f..95cfe7d2b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 207a5805ba..e834130151 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1789,6 +1792,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1837,6 +1841,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1846,14 +1851,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d02fd83c0a..7dbbbc629b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 234fb66580..e1065db5c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5139,7 +5139,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5191,7 +5191,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5270,7 +5270,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5318,7 +5318,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5379,6 +5379,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5387,7 +5388,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5456,7 +5457,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6bda383bea..99c8d4f611 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v20-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (5.0K, ../../CA+HiwqGFCcapwkC4eYLJzaAQbonoYW4+JaVjxis+bhxTL6Aw=A@mail.gmail.com/3-v20-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From 02fc2bb3c52f04b60c8da9d8682ba54c2a303137 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v20 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 src/include/nodes/meson.build          |  1 +
 8 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b7df232081..94701af8e1 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -19,6 +19,7 @@ node_support_input_i = [
   'nodes/supportnodes.h',
   'nodes/value.h',
   'utils/rel.h',
+  'nodes/bitmapset.h',
 ]
 
 node_support_input = []
-- 
2.35.3



  [application/octet-stream] v20-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqGFCcapwkC4eYLJzaAQbonoYW4+JaVjxis+bhxTL6Aw=A@mail.gmail.com/4-v20-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From e9925de0df0a5803c75cfc4b081d93404708d03d Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v20 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index cc9e39c4a5..b6c3749395 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7dbbbc629b..156c033bda 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 55dcd668c9..ddaefdd91d 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1195,10 +1195,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1221,10 +1221,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1247,10 +1247,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1273,10 +1273,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1299,10 +1299,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1324,10 +1324,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1336,10 +1336,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



  [application/octet-stream] v20-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (27.7K, ../../CA+HiwqGFCcapwkC4eYLJzaAQbonoYW4+JaVjxis+bhxTL6Aw=A@mail.gmail.com/5-v20-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 65c6d0e2808ad5d723036a3a9bb9cda72af51b13 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v20 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++--
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 17 +++++++
 src/backend/optimizer/prep/preptlist.c   | 49 ++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 64 +++++++++++++++---------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 -----------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 138 insertions(+), 88 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 04454ad6e6..e034e3ceee 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..bcd380516d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -532,7 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f854855951..5c1c7aed2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9576b69f1a..8913200879 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,24 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, resultRelation,
+															   extraUpdatedCols,
+															   this_result_rel->top_parent_relids);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+														  extraUpdatedCols);
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1879,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1896,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1938,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..10a948ed40 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,43 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in target_perminfo->updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	/* These go into ModifyTable.extraUpdatedColsBitmaps, so make as Nodes. */
+	Bitmapset *extraUpdatedCols = makeNode(Bitmapset);
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 10c2aa13f6..ea632a94a6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -656,7 +655,8 @@ static Bitmapset *
 translate_col_privs(const Bitmapset *parent_privs,
 					List *translated_vars)
 {
-	Bitmapset  *child_privs = NULL;
+	/* These go into ModifyTable.extraUpdatedColsBitmaps, so make as Nodes. */
+	Bitmapset  *child_privs = makeNode(Bitmapset);
 	bool		whole_row;
 	int			attno;
 	ListCell   *lc;
@@ -866,13 +866,16 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
-translate_col_privs_recurse(PlannerInfo *root, Index relid,
-							Bitmapset *top_parent_cols,
-							Relids top_parent_relids)
+Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, Index relid,
+							   Bitmapset *top_parent_cols,
+							   Relids top_parent_relids)
 {
 	AppendRelInfo *appinfo;
 
+	if (top_parent_cols == NULL)
+		return NULL;
+
 	Assert(root->append_rel_array != NULL);
 	appinfo = root->append_rel_array[relid];
 	Assert(appinfo != NULL);
@@ -883,9 +886,9 @@ translate_col_privs_recurse(PlannerInfo *root, Index relid,
 	 * and child relation pairs.
 	 */
 	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
-		translate_col_privs_recurse(root, appinfo->parent_relid,
-									top_parent_cols,
-									top_parent_relids);
+		translate_col_privs_multilevel(root, appinfo->parent_relid,
+									   top_parent_cols,
+									   top_parent_relids);
 
 	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
 }
@@ -903,6 +906,8 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 	Bitmapset *updatedCols,
 			  *extraUpdatedCols;
 
+	Assert(root->parse->commandType == CMD_UPDATE);
+
 	if (!IS_SIMPLE_REL(rel))
 		return NULL;
 
@@ -918,24 +923,33 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 	 * translate the columns found therein to match the given relation.
 	 */
 	if (rel->top_parent_relids != NULL)
+	{
 		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
 								root);
+	}
 	else
+	{
+		Assert(rel->relid == root->parse->resultRelation);
 		rte = planner_rt_fetch(rel->relid, root);
+	}
 
 	Assert(rte->perminfoindex > 0);
 	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
 
 	if (rel->top_parent_relids != NULL)
-		updatedCols = translate_col_privs_recurse(root, rel->relid,
-												  perminfo->updatedCols,
-												  rel->top_parent_relids);
+	{
+		updatedCols = translate_col_privs_multilevel(root, rel->relid,
+													 perminfo->updatedCols,
+													 rel->top_parent_relids);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel->relid,
+														  root->extraUpdatedCols,
+														  rel->top_parent_relids);
+	}
 	else
+	{
 		updatedCols = perminfo->updatedCols;
-
-	/* extraUpdatedCols can be obtained directly from the RTE. */
-	rte = planner_rt_fetch(rel->relid, root);
-	extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
+	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 70f61ae7b1..f70fe2736c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3643,6 +3643,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3658,6 +3660,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3722,6 +3725,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e834130151..b75455382e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1791,7 +1792,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1840,7 +1840,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1858,7 +1857,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 156c033bda..d62d457fc0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1592,46 +1592,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3670,7 +3630,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3682,7 +3641,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3767,9 +3725,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 080680ecd0..118a150d3c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1152,7 +1152,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 99c8d4f611..04c7403897 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaff24256e..2f8c9f65cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..3cc54ef7e3 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, Index relid,
+							   Bitmapset *top_parent_cols,
+							   Relids top_parent_relids);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-07 04:25  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-07 04:25 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Oct 7, 2022 at 10:04 AM Amit Langote <[email protected]> wrote:
> On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> > Actually, List of Bitmapsets turned out to be something that doesn't
> > just-work with our Node infrastructure, which I found out thanks to
> > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > first-class support for copy/equal/write/read support for Bitmapsets,
> > such that writeNode() can write appropriately labeled versions of them
> > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > didn't actually go ahead and make *all* Bitmapsets in the plan trees
> > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > just use *_NODE_FIELD().
>
> All meson builds on the cfbot machines seem to have failed, maybe
> because I didn't update src/include/nodes/meson.build to add
> 'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
> an updated version assuming that's the problem.  (Will set up meson
> builds on my machine to avoid this in the future.)

And... noticed that a postgres_fdw test failed, because
_readBitmapset() not having been changed to set NodeTag would
"corrupt" any Bitmapsets that were created with it set.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v21-0001-Rework-query-relation-permission-checking.patch (145.6K, ../../CA+HiwqHDpknDhOO5a1egdUv77qHJYzM9-VRAxHgjuyk+zGd1SA@mail.gmail.com/2-v21-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From fe5ee4bc42b1d5e3ee506e8b5503650d7c376fc2 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v21 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 155 +++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 951 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d013f5b1a..de913fc4ae 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1512,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1812,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1890,6 +1892,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1901,6 +1933,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1941,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1965,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1977,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2145,6 +2181,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2222,6 +2259,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2238,7 +2277,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2624,7 +2664,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2644,13 +2683,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3953,12 +3991,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3970,12 +4008,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 175aa837f2..575ac5c81d 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1f774ac065..c2f50ae215 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12303,7 +12308,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18024,7 +18030,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18951,7 +18958,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d0fd6e072..9576b69f1a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6210,6 +6214,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6337,6 +6342,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..10c2aa13f6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,83 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+	else
+		updatedCols = perminfo->updatedCols;
+
+	/* extraUpdatedCols can be obtained directly from the RTE. */
+	rte = planner_rt_fetch(rel->relid, root);
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c2b5474f5f..95cfe7d2b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 207a5805ba..e834130151 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1789,6 +1792,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1837,6 +1841,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1846,14 +1851,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d02fd83c0a..7dbbbc629b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 234fb66580..e1065db5c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5139,7 +5139,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5191,7 +5191,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5270,7 +5270,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5318,7 +5318,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5379,6 +5379,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5387,7 +5388,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5456,7 +5457,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6bda383bea..99c8d4f611 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v21-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (5.5K, ../../CA+HiwqHDpknDhOO5a1egdUv77qHJYzM9-VRAxHgjuyk+zGd1SA@mail.gmail.com/3-v21-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From be35773af5f29b9406d052238b79dc0a08dcef83 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v21 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/nodes/readfuncs.c          |  4 ++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 src/include/nodes/meson.build          |  1 +
 9 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..e5c993c90d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -230,6 +230,10 @@ _readBitmapset(void)
 		result = bms_add_member(result, val);
 	}
 
+	/* XXX maybe do `result = makeNode(Bitmapset);` at the top? */
+	if (result)
+		result->type = T_Bitmapset;
+
 	return result;
 }
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b7df232081..94701af8e1 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -19,6 +19,7 @@ node_support_input_i = [
   'nodes/supportnodes.h',
   'nodes/value.h',
   'utils/rel.h',
+  'nodes/bitmapset.h',
 ]
 
 node_support_input = []
-- 
2.35.3



  [application/octet-stream] v21-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqHDpknDhOO5a1egdUv77qHJYzM9-VRAxHgjuyk+zGd1SA@mail.gmail.com/4-v21-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 3c0c6b3b16787fc427430fa610225dbf7b982760 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v21 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index cc9e39c4a5..b6c3749395 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7dbbbc629b..156c033bda 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 170bea23c2..3d1d26aa39 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1212,10 +1212,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1238,10 +1238,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1264,10 +1264,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1290,10 +1290,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1316,10 +1316,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1341,10 +1341,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1353,10 +1353,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



  [application/octet-stream] v21-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (28.4K, ../../CA+HiwqHDpknDhOO5a1egdUv77qHJYzM9-VRAxHgjuyk+zGd1SA@mail.gmail.com/5-v21-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 345391612383e4d9a8ce210a787d65b92a1d2241 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v21 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 +--
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 +-
 src/backend/optimizer/plan/planner.c     | 26 +++++++
 src/backend/optimizer/prep/preptlist.c   | 48 +++++++++++++
 src/backend/optimizer/util/inherit.c     | 89 +++++++++++++-----------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +-
 src/backend/rewrite/rewriteHandler.c     | 45 ------------
 src/include/nodes/execnodes.h            |  3 +
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 +
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 +
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 157 insertions(+), 102 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 04454ad6e6..e034e3ceee 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e5c993c90d..aa7077c7f8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -536,7 +536,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f854855951..5c1c7aed2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9576b69f1a..cf5b895a28 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,33 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+							extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+														  extraUpdatedCols);
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1888,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1905,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1947,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..ee5c9a1d82 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,42 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in target_perminfo->updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 10c2aa13f6..65daec5f02 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,28 +865,35 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
-translate_col_privs_recurse(PlannerInfo *root, Index relid,
-							Bitmapset *top_parent_cols,
-							Relids top_parent_relids)
+Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
 {
+	Bitmapset *result;
 	AppendRelInfo *appinfo;
 
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
 	Assert(root->append_rel_array != NULL);
-	appinfo = root->append_rel_array[relid];
+	appinfo = root->append_rel_array[rel->relid];
 	Assert(appinfo != NULL);
 
-	/*
-	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
-	 * because appinfo->translated_vars maps between directly related parent
-	 * and child relation pairs.
-	 */
-	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
-		translate_col_privs_recurse(root, appinfo->parent_relid,
-									top_parent_cols,
-									top_parent_relids);
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
 
-	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+	return result;
 }
 
 /*
@@ -898,11 +904,14 @@ translate_col_privs_recurse(PlannerInfo *root, Index relid,
 Bitmapset *
 GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 {
+	Index	use_relid;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *perminfo;
 	Bitmapset *updatedCols,
 			  *extraUpdatedCols;
 
+	Assert(root->parse->commandType == CMD_UPDATE);
+
 	if (!IS_SIMPLE_REL(rel))
 		return NULL;
 
@@ -917,25 +926,27 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 	 * query, because only that one gets assigned a RTEPermissionInfo, and
 	 * translate the columns found therein to match the given relation.
 	 */
-	if (rel->top_parent_relids != NULL)
-		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
-								root);
-	else
-		rte = planner_rt_fetch(rel->relid, root);
-
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
 	Assert(rte->perminfoindex > 0);
 	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
 
-	if (rel->top_parent_relids != NULL)
-		updatedCols = translate_col_privs_recurse(root, rel->relid,
-												  perminfo->updatedCols,
-												  rel->top_parent_relids);
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  root->extraUpdatedCols);
+	}
 	else
+	{
 		updatedCols = perminfo->updatedCols;
-
-	/* extraUpdatedCols can be obtained directly from the RTE. */
-	rte = planner_rt_fetch(rel->relid, root);
-	extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
+	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 70f61ae7b1..f70fe2736c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3643,6 +3643,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3658,6 +3660,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3722,6 +3725,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e834130151..b75455382e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1791,7 +1792,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1840,7 +1840,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1858,7 +1857,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 156c033bda..d62d457fc0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1592,46 +1592,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3670,7 +3630,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3682,7 +3641,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3767,9 +3725,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 080680ecd0..118a150d3c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1152,7 +1152,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 99c8d4f611..04c7403897 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaff24256e..2f8c9f65cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-07 06:49  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-07 06:49 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Oct 7, 2022 at 1:25 PM Amit Langote <[email protected]> wrote:
> On Fri, Oct 7, 2022 at 10:04 AM Amit Langote <[email protected]> wrote:
> > On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> > > Actually, List of Bitmapsets turned out to be something that doesn't
> > > just-work with our Node infrastructure, which I found out thanks to
> > > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > > first-class support for copy/equal/write/read support for Bitmapsets,
> > > such that writeNode() can write appropriately labeled versions of them
> > > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > > didn't actually go ahead and make *all* Bitmapsets in the plan trees
> > > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > > just use *_NODE_FIELD().
> >
> > All meson builds on the cfbot machines seem to have failed, maybe
> > because I didn't update src/include/nodes/meson.build to add
> > 'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
> > an updated version assuming that's the problem.  (Will set up meson
> > builds on my machine to avoid this in the future.)
>
> And... noticed that a postgres_fdw test failed, because
> _readBitmapset() not having been changed to set NodeTag would
> "corrupt" any Bitmapsets that were created with it set.

Broke the other cases while fixing the above.  Attaching a new version
again.  In the latest version, I'm setting Bitmapset.type by hand with
an XXX comment nearby saying that it would be nice to change that to
makeNode(Bitmapset), which I know sounds pretty ad-hoc.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v22-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (28.4K, ../../CA+HiwqH91T9o+5gdS3rN3HLQ0NZzoPxUsfYRUx2FV2BF40u2rQ@mail.gmail.com/2-v22-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 345391612383e4d9a8ce210a787d65b92a1d2241 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v22 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 +--
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 +-
 src/backend/optimizer/plan/planner.c     | 26 +++++++
 src/backend/optimizer/prep/preptlist.c   | 48 +++++++++++++
 src/backend/optimizer/util/inherit.c     | 89 +++++++++++++-----------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +-
 src/backend/rewrite/rewriteHandler.c     | 45 ------------
 src/include/nodes/execnodes.h            |  3 +
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 +
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 +
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 157 insertions(+), 102 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 04454ad6e6..e034e3ceee 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e5c993c90d..aa7077c7f8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -536,7 +536,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f854855951..5c1c7aed2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9576b69f1a..cf5b895a28 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,33 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+							extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+														  extraUpdatedCols);
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1888,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1905,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1947,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..ee5c9a1d82 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,42 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in target_perminfo->updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 10c2aa13f6..65daec5f02 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,28 +865,35 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
-translate_col_privs_recurse(PlannerInfo *root, Index relid,
-							Bitmapset *top_parent_cols,
-							Relids top_parent_relids)
+Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
 {
+	Bitmapset *result;
 	AppendRelInfo *appinfo;
 
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
 	Assert(root->append_rel_array != NULL);
-	appinfo = root->append_rel_array[relid];
+	appinfo = root->append_rel_array[rel->relid];
 	Assert(appinfo != NULL);
 
-	/*
-	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
-	 * because appinfo->translated_vars maps between directly related parent
-	 * and child relation pairs.
-	 */
-	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
-		translate_col_privs_recurse(root, appinfo->parent_relid,
-									top_parent_cols,
-									top_parent_relids);
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
 
-	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+	return result;
 }
 
 /*
@@ -898,11 +904,14 @@ translate_col_privs_recurse(PlannerInfo *root, Index relid,
 Bitmapset *
 GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 {
+	Index	use_relid;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *perminfo;
 	Bitmapset *updatedCols,
 			  *extraUpdatedCols;
 
+	Assert(root->parse->commandType == CMD_UPDATE);
+
 	if (!IS_SIMPLE_REL(rel))
 		return NULL;
 
@@ -917,25 +926,27 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 	 * query, because only that one gets assigned a RTEPermissionInfo, and
 	 * translate the columns found therein to match the given relation.
 	 */
-	if (rel->top_parent_relids != NULL)
-		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
-								root);
-	else
-		rte = planner_rt_fetch(rel->relid, root);
-
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
 	Assert(rte->perminfoindex > 0);
 	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
 
-	if (rel->top_parent_relids != NULL)
-		updatedCols = translate_col_privs_recurse(root, rel->relid,
-												  perminfo->updatedCols,
-												  rel->top_parent_relids);
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  root->extraUpdatedCols);
+	}
 	else
+	{
 		updatedCols = perminfo->updatedCols;
-
-	/* extraUpdatedCols can be obtained directly from the RTE. */
-	rte = planner_rt_fetch(rel->relid, root);
-	extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
+	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 70f61ae7b1..f70fe2736c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3643,6 +3643,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3658,6 +3660,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3722,6 +3725,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e834130151..b75455382e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1791,7 +1792,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1840,7 +1840,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1858,7 +1857,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 156c033bda..d62d457fc0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1592,46 +1592,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3670,7 +3630,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3682,7 +3641,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3767,9 +3725,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 080680ecd0..118a150d3c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1152,7 +1152,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 99c8d4f611..04c7403897 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaff24256e..2f8c9f65cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



  [application/octet-stream] v22-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (5.5K, ../../CA+HiwqH91T9o+5gdS3rN3HLQ0NZzoPxUsfYRUx2FV2BF40u2rQ@mail.gmail.com/3-v22-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From be35773af5f29b9406d052238b79dc0a08dcef83 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v22 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/nodes/readfuncs.c          |  4 ++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 src/include/nodes/meson.build          |  1 +
 9 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..e5c993c90d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -230,6 +230,10 @@ _readBitmapset(void)
 		result = bms_add_member(result, val);
 	}
 
+	/* XXX maybe do `result = makeNode(Bitmapset);` at the top? */
+	if (result)
+		result->type = T_Bitmapset;
+
 	return result;
 }
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b7df232081..94701af8e1 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -19,6 +19,7 @@ node_support_input_i = [
   'nodes/supportnodes.h',
   'nodes/value.h',
   'utils/rel.h',
+  'nodes/bitmapset.h',
 ]
 
 node_support_input = []
-- 
2.35.3



  [application/octet-stream] v22-0001-Rework-query-relation-permission-checking.patch (145.6K, ../../CA+HiwqH91T9o+5gdS3rN3HLQ0NZzoPxUsfYRUx2FV2BF40u2rQ@mail.gmail.com/4-v22-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From fe5ee4bc42b1d5e3ee506e8b5503650d7c376fc2 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v22 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 155 +++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 951 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d013f5b1a..de913fc4ae 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1512,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1812,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1890,6 +1892,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1901,6 +1933,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1941,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1965,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1977,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2145,6 +2181,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2222,6 +2259,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2238,7 +2277,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2624,7 +2664,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2644,13 +2683,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3953,12 +3991,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3970,12 +4008,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 175aa837f2..575ac5c81d 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1f774ac065..c2f50ae215 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12303,7 +12308,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18024,7 +18030,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18951,7 +18958,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d0fd6e072..9576b69f1a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6210,6 +6214,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6337,6 +6342,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..10c2aa13f6 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,83 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_recurse
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_recurse(PlannerInfo *root, Index relid,
+							Bitmapset *top_parent_cols,
+							Relids top_parent_relids)
+{
+	AppendRelInfo *appinfo;
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[relid];
+	Assert(appinfo != NULL);
+
+	/*
+	 * Must recurse if 'relid' doesn't appear to the parent's direct child,
+	 * because appinfo->translated_vars maps between directly related parent
+	 * and child relation pairs.
+	 */
+	if (bms_singleton_member(top_parent_relids) != appinfo->parent_relid)
+		translate_col_privs_recurse(root, appinfo->parent_relid,
+									top_parent_cols,
+									top_parent_relids);
+
+	return translate_col_privs(top_parent_cols, appinfo->translated_vars);
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	if (rel->top_parent_relids != NULL)
+		rte =  planner_rt_fetch(bms_singleton_member(rel->top_parent_relids),
+								root);
+	else
+		rte = planner_rt_fetch(rel->relid, root);
+
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (rel->top_parent_relids != NULL)
+		updatedCols = translate_col_privs_recurse(root, rel->relid,
+												  perminfo->updatedCols,
+												  rel->top_parent_relids);
+	else
+		updatedCols = perminfo->updatedCols;
+
+	/* extraUpdatedCols can be obtained directly from the RTE. */
+	rte = planner_rt_fetch(rel->relid, root);
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c2b5474f5f..95cfe7d2b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 207a5805ba..e834130151 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1789,6 +1792,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1837,6 +1841,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1846,14 +1851,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d02fd83c0a..7dbbbc629b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 234fb66580..e1065db5c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5139,7 +5139,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5191,7 +5191,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5270,7 +5270,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5318,7 +5318,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5379,6 +5379,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5387,7 +5388,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5456,7 +5457,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6bda383bea..99c8d4f611 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v22-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqH91T9o+5gdS3rN3HLQ0NZzoPxUsfYRUx2FV2BF40u2rQ@mail.gmail.com/5-v22-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 3c0c6b3b16787fc427430fa610225dbf7b982760 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v22 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index cc9e39c4a5..b6c3749395 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7dbbbc629b..156c033bda 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 170bea23c2..3d1d26aa39 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1212,10 +1212,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1238,10 +1238,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1264,10 +1264,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1290,10 +1290,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1316,10 +1316,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1341,10 +1341,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1353,10 +1353,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-07 07:31  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-07 07:31 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Oct 7, 2022 at 3:49 PM Amit Langote <[email protected]> wrote:
> On Fri, Oct 7, 2022 at 1:25 PM Amit Langote <[email protected]> wrote:
> > On Fri, Oct 7, 2022 at 10:04 AM Amit Langote <[email protected]> wrote:
> > > On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> > > > Actually, List of Bitmapsets turned out to be something that doesn't
> > > > just-work with our Node infrastructure, which I found out thanks to
> > > > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > > > first-class support for copy/equal/write/read support for Bitmapsets,
> > > > such that writeNode() can write appropriately labeled versions of them
> > > > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > > > didn't actually go ahead and make *all* Bitmapsets in the plan trees
> > > > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > > > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > > > just use *_NODE_FIELD().
> > >
> > > All meson builds on the cfbot machines seem to have failed, maybe
> > > because I didn't update src/include/nodes/meson.build to add
> > > 'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
> > > an updated version assuming that's the problem.  (Will set up meson
> > > builds on my machine to avoid this in the future.)
> >
> > And... noticed that a postgres_fdw test failed, because
> > _readBitmapset() not having been changed to set NodeTag would
> > "corrupt" any Bitmapsets that were created with it set.
>
> Broke the other cases while fixing the above.  Attaching a new version
> again.  In the latest version, I'm setting Bitmapset.type by hand with
> an XXX comment nearby saying that it would be nice to change that to
> makeNode(Bitmapset), which I know sounds pretty ad-hoc.

Sorry, I attached the wrong patches with the last email.  The
"correct" v22 attached this time.

Wondering if it might be a good idea to reorder the patches such that
the changes that move extraUpdatedCols out of RangeTblEntry (patches
0003 and 0004) can be considered/applied independently of the changes
that move permission-checking-related fields out of RangeTblEntry
(patch 0001).  The former seem more straightforward except of course
the Bitmapset node infrastructure changes.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v22-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.6K, ../../CA+HiwqH80qX1ZLx3HyHmBrOzLQeuKuGx6FzGep0F_9zw9L4PAA@mail.gmail.com/2-v22-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 6bed3577a3d775d097ff95abee3993a0015d3d48 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v22 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 728 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 729 insertions(+), 816 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index cc9e39c4a5..b6c3749395 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7dbbbc629b..156c033bda 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1714,7 +1714,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1826,17 +1827,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1848,9 +1858,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9dd137415e..19ef0a6b80 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1798,13 +1798,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1817,57 +1817,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1890,8 +1890,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1900,13 +1900,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2016,16 +2016,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2063,26 +2063,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2101,41 +2101,41 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2145,68 +2145,68 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2223,19 +2223,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2245,19 +2245,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2301,64 +2301,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2543,24 +2543,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3080,7 +3080,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3119,8 +3119,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3132,8 +3132,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3145,8 +3145,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3158,8 +3158,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index d57eeb761c..b49f091d2f 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1903,19 +1903,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1956,17 +1956,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -1996,17 +1996,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2037,16 +2037,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2068,15 +2068,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 170bea23c2..3d1d26aa39 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1212,10 +1212,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1238,10 +1238,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1264,10 +1264,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1290,10 +1290,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1316,10 +1316,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1341,10 +1341,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1353,10 +1353,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



  [application/octet-stream] v22-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (26.1K, ../../CA+HiwqH80qX1ZLx3HyHmBrOzLQeuKuGx6FzGep0F_9zw9L4PAA@mail.gmail.com/3-v22-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From a4740a73ceb8594f4088313ad3fb8a98521b4cd4 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v22 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++---
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 38 +++++++++++++++++++
 src/backend/optimizer/prep/preptlist.c   | 48 ++++++++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 25 ++++++------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 ----------------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 131 insertions(+), 76 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 04454ad6e6..e034e3ceee 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e5c993c90d..aa7077c7f8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -536,7 +536,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f854855951..5c1c7aed2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9576b69f1a..5efa21ea18 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,35 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+						{
+							extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+															  extraUpdatedCols);
+						}
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1890,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						/* See the comment in the inherited UPDATE block. */
+						if (root->extraUpdatedCols)
+						{
+							root->extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+						}
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1912,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					/* See the comment in the inherited UPDATE block. */
+					if (root->extraUpdatedCols)
+					{
+						root->extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1959,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..ee5c9a1d82 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,42 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in target_perminfo->updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 47d449621b..43e10027a3 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,7 +865,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
+Bitmapset *
 translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
 							   RelOptInfo *top_parent_rel,
 							   Bitmapset *top_parent_cols)
@@ -941,12 +940,12 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
 													 perminfo->updatedCols);
 		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
-														  rte->extraUpdatedCols);
+														  root->extraUpdatedCols);
 	}
 	else
 	{
 		updatedCols = perminfo->updatedCols;
-		extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
 	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 70f61ae7b1..f70fe2736c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3643,6 +3643,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3658,6 +3660,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3722,6 +3725,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e834130151..b75455382e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1791,7 +1792,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1840,7 +1840,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1858,7 +1857,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 156c033bda..d62d457fc0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1592,46 +1592,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3670,7 +3630,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3682,7 +3641,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3767,9 +3725,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 080680ecd0..118a150d3c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1152,7 +1152,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 99c8d4f611..04c7403897 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaff24256e..2f8c9f65cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



  [application/octet-stream] v22-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (5.5K, ../../CA+HiwqH80qX1ZLx3HyHmBrOzLQeuKuGx6FzGep0F_9zw9L4PAA@mail.gmail.com/4-v22-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From 488859351c27a57e749b1cd67dd2d28b174644e7 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v22 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/nodes/readfuncs.c          |  4 ++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 src/include/nodes/meson.build          |  1 +
 9 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..e5c993c90d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -230,6 +230,10 @@ _readBitmapset(void)
 		result = bms_add_member(result, val);
 	}
 
+	/* XXX maybe do `result = makeNode(Bitmapset);` at the top? */
+	if (result)
+		result->type = T_Bitmapset;
+
 	return result;
 }
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b7df232081..94701af8e1 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -19,6 +19,7 @@ node_support_input_i = [
   'nodes/supportnodes.h',
   'nodes/value.h',
   'utils/rel.h',
+  'nodes/bitmapset.h',
 ]
 
 node_support_input = []
-- 
2.35.3



  [application/octet-stream] v22-0001-Rework-query-relation-permission-checking.patch (145.8K, ../../CA+HiwqH80qX1ZLx3HyHmBrOzLQeuKuGx6FzGep0F_9zw9L4PAA@mail.gmail.com/5-v22-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 9aad8b772fb3660731c0c36737f46eeea424c42c Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v22 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 167 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 963 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d013f5b1a..de913fc4ae 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1512,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1812,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1890,6 +1892,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1901,6 +1933,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1941,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1965,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1977,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2145,6 +2181,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2222,6 +2259,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2238,7 +2277,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2624,7 +2664,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2644,13 +2683,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3953,12 +3991,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3970,12 +4008,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 49924e476a..5a62d5641d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 175aa837f2..575ac5c81d 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -657,6 +657,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1380,9 +1386,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1f774ac065..c2f50ae215 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10020,7 +10023,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10198,7 +10202,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12303,7 +12308,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18024,7 +18030,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18951,7 +18958,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d0fd6e072..9576b69f1a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6210,6 +6214,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6337,6 +6342,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..47d449621b 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,95 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index	use_relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  rte->extraUpdatedCols);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = rte->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c2b5474f5f..95cfe7d2b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 207a5805ba..e834130151 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -492,6 +493,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1789,6 +1792,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1837,6 +1841,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1846,14 +1851,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d02fd83c0a..7dbbbc629b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -352,6 +352,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -394,32 +395,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1590,10 +1594,13 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1616,7 +1623,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1707,8 +1714,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1749,18 +1755,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1824,12 +1818,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1847,28 +1835,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1897,8 +1866,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3039,6 +3012,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3175,6 +3151,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3246,57 +3223,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3400,7 +3379,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3411,8 +3390,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3686,6 +3663,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3697,6 +3675,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3783,7 +3762,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 234fb66580..e1065db5c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5139,7 +5139,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5191,7 +5191,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5270,7 +5270,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5318,7 +5318,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5379,6 +5379,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5387,7 +5388,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5456,7 +5457,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37c6032ae..4fbb8801ff 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -93,7 +93,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6bda383bea..99c8d4f611 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-12 13:49  Peter Eisentraut <[email protected]>
  parent: Amit Langote <[email protected]>
  2 siblings, 1 reply; 73+ messages in thread

From: Peter Eisentraut @ 2022-10-12 13:49 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On 06.10.22 15:29, Amit Langote wrote:
> I tried in the attached 0004.  ModifyTable gets a new member
> extraUpdatedColsBitmaps, which is List of Bitmapset "nodes".
> 
> Actually, List of Bitmapsets turned out to be something that doesn't
> just-work with our Node infrastructure, which I found out thanks to
> -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> first-class support for copy/equal/write/read support for Bitmapsets,
> such that writeNode() can write appropriately labeled versions of them
> and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> didn't actually go ahead and make*all*  Bitmapsets in the plan trees
> to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> just use *_NODE_FIELD().

Seeing that on 64-bit platforms we have a 4-byte padding gap in the 
Bitmapset struct, sticking a node tag in there seems pretty sensible. 
So turning Bitmapset into a proper Node and then making the other 
adjustments you describe makes sense to me.

Making a new thread about this might be best.

(I can't currently comment on the rest of the patch set.  So I don't 
know if you'll really end up needing lists of bitmapsets.  But from here 
it looks like turning bitmapsets into nodes might be a worthwhile change 
just by itself.)






^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-13 08:14  Amit Langote <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-10-13 08:14 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Oct 12, 2022 at 10:50 PM Peter Eisentraut
<[email protected]> wrote:
> On 06.10.22 15:29, Amit Langote wrote:
> > I tried in the attached 0004.  ModifyTable gets a new member
> > extraUpdatedColsBitmaps, which is List of Bitmapset "nodes".
> >
> > Actually, List of Bitmapsets turned out to be something that doesn't
> > just-work with our Node infrastructure, which I found out thanks to
> > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > first-class support for copy/equal/write/read support for Bitmapsets,
> > such that writeNode() can write appropriately labeled versions of them
> > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > didn't actually go ahead and make*all*  Bitmapsets in the plan trees
> > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > just use *_NODE_FIELD().
>
> Seeing that on 64-bit platforms we have a 4-byte padding gap in the
> Bitmapset struct, sticking a node tag in there seems pretty sensible.
> So turning Bitmapset into a proper Node and then making the other
> adjustments you describe makes sense to me.
>
> Making a new thread about this might be best.
>
> (I can't currently comment on the rest of the patch set.  So I don't
> know if you'll really end up needing lists of bitmapsets.  But from here
> it looks like turning bitmapsets into nodes might be a worthwhile change
> just by itself.)

Ok, thanks.  I'll start a new thread about it.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-10-15 06:00  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-10-15 06:00 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Oct 7, 2022 at 4:31 PM Amit Langote <[email protected]> wrote:
> On Fri, Oct 7, 2022 at 3:49 PM Amit Langote <[email protected]> wrote:
> > On Fri, Oct 7, 2022 at 1:25 PM Amit Langote <[email protected]> wrote:
> > > On Fri, Oct 7, 2022 at 10:04 AM Amit Langote <[email protected]> wrote:
> > > > On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> > > > > Actually, List of Bitmapsets turned out to be something that doesn't
> > > > > just-work with our Node infrastructure, which I found out thanks to
> > > > > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > > > > first-class support for copy/equal/write/read support for Bitmapsets,
> > > > > such that writeNode() can write appropriately labeled versions of them
> > > > > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > > > > didn't actually go ahead and make *all* Bitmapsets in the plan trees
> > > > > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > > > > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > > > > just use *_NODE_FIELD().
> > > >
> > > > All meson builds on the cfbot machines seem to have failed, maybe
> > > > because I didn't update src/include/nodes/meson.build to add
> > > > 'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
> > > > an updated version assuming that's the problem.  (Will set up meson
> > > > builds on my machine to avoid this in the future.)
> > >
> > > And... noticed that a postgres_fdw test failed, because
> > > _readBitmapset() not having been changed to set NodeTag would
> > > "corrupt" any Bitmapsets that were created with it set.
> >
> > Broke the other cases while fixing the above.  Attaching a new version
> > again.  In the latest version, I'm setting Bitmapset.type by hand with
> > an XXX comment nearby saying that it would be nice to change that to
> > makeNode(Bitmapset), which I know sounds pretty ad-hoc.
>
> Sorry, I attached the wrong patches with the last email.  The
> "correct" v22 attached this time.

Rebased over c037471832.


--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v23-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (26.1K, ../../CA+HiwqGdtO_FmS2AJtCWXbsKW6QhL9bkzXGKgYWXgBimUEa+6w@mail.gmail.com/2-v23-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 815230e436f504111a3789843292529dfac23d4e Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v23 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++---
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 38 +++++++++++++++++++
 src/backend/optimizer/prep/preptlist.c   | 48 ++++++++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 25 ++++++------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 ----------------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 131 insertions(+), 76 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 447f7bc2fb..20057ed3a2 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e5c993c90d..aa7077c7f8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -536,7 +536,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f854855951..5c1c7aed2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9576b69f1a..5efa21ea18 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,35 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+						{
+							extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+															  extraUpdatedCols);
+						}
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1890,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						/* See the comment in the inherited UPDATE block. */
+						if (root->extraUpdatedCols)
+						{
+							root->extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+						}
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1912,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					/* See the comment in the inherited UPDATE block. */
+					if (root->extraUpdatedCols)
+					{
+						root->extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1959,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..ee5c9a1d82 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,42 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in target_perminfo->updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 47d449621b..43e10027a3 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,7 +865,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
+Bitmapset *
 translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
 							   RelOptInfo *top_parent_rel,
 							   Bitmapset *top_parent_cols)
@@ -941,12 +940,12 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
 													 perminfo->updatedCols);
 		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
-														  rte->extraUpdatedCols);
+														  root->extraUpdatedCols);
 	}
 	else
 	{
 		updatedCols = perminfo->updatedCols;
-		extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
 	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 70f61ae7b1..f70fe2736c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3643,6 +3643,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3658,6 +3660,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3722,6 +3725,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 1e5e2abcda..5e4fa0f45c 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1815,7 +1816,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1864,7 +1864,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1882,7 +1881,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 77cd294e51..06cacc3ecc 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1626,46 +1626,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3704,7 +3664,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3716,7 +3675,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3801,9 +3759,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 080680ecd0..118a150d3c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1152,7 +1152,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 99c8d4f611..04c7403897 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaff24256e..2f8c9f65cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



  [application/octet-stream] v23-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (120.9K, ../../CA+HiwqGdtO_FmS2AJtCWXbsKW6QhL9bkzXGKgYWXgBimUEa+6w@mail.gmail.com/3-v23-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 661d7e033cd3a526e5553224bee3efe501776936 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v23 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 740 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 735 insertions(+), 822 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 9746998751..8b0b1e9627 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 5e9d226e54..ac2568e59a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -786,13 +786,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fda0eacf79..77cd294e51 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1748,7 +1748,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1860,17 +1861,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1882,9 +1892,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a869321cdf..934a731205 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2225,7 +2225,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2241,7 +2241,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2257,7 +2257,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2273,7 +2273,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2291,7 +2291,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3283,7 +3283,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bf4ff30d86..8f81a3098e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2054,7 +2054,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2106,19 +2106,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index bfcd8ac9a0..43f2130da3 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,56 +1302,56 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1364,22 +1364,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1419,14 +1419,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1448,10 +1448,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1697,23 +1697,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1727,10 +1727,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1801,13 +1801,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1820,57 +1820,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1893,8 +1893,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1903,13 +1903,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2019,16 +2019,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2066,26 +2066,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2104,44 +2104,44 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.last_idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.last_seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.last_idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    last_seq_scan,
+    seq_tup_read,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2151,71 +2151,71 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.last_idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.last_seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.last_idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    last_seq_scan,
+    seq_tup_read,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2232,19 +2232,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2254,19 +2254,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2310,64 +2310,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2552,24 +2552,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3089,7 +3089,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3128,8 +3128,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3141,8 +3141,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3154,8 +3154,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3167,8 +3167,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8b8eadd181..019c4726f6 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 5a47dacad9..2b578cced1 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1925,19 +1925,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1978,17 +1978,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -2018,17 +2018,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2059,16 +2059,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2090,15 +2090,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 170bea23c2..3d1d26aa39 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1212,10 +1212,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1238,10 +1238,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1264,10 +1264,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1290,10 +1290,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1316,10 +1316,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1341,10 +1341,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1353,10 +1353,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7f2e32d8b0..f40197acbf 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -882,9 +882,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1404,9 +1404,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1426,9 +1426,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



  [application/octet-stream] v23-0001-Rework-query-relation-permission-checking.patch (145.8K, ../../CA+HiwqGdtO_FmS2AJtCWXbsKW6QhL9bkzXGKgYWXgBimUEa+6w@mail.gmail.com/4-v23-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From c691e011debbaf7948ce33e0e95be21c77540f87 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v23 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 167 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 963 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index d98709e5e8..5e97d0331f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1512,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1812,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1890,6 +1892,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1901,6 +1933,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1941,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1965,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1977,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2154,6 +2190,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2231,6 +2268,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2247,7 +2286,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2633,7 +2673,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2653,13 +2692,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3962,12 +4000,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3979,12 +4017,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 87fdd972c2..129442b96e 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..5ae68842df 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a079c70152..03f8ec459a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd56066c13..622b574860 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 20135ef1b0..a03dbae174 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10022,7 +10025,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10211,7 +10215,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12316,7 +12321,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18037,7 +18043,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18964,7 +18971,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ab4d8e201d..f854855951 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5d0fd6e072..9576b69f1a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6210,6 +6214,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6337,6 +6342,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 41c7066d90..607f7ae907 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1345,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index cf7691a474..47d449621b 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,95 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index	use_relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  rte->extraUpdatedCols);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = rte->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index edcdd0a360..7711075ac2 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c2b5474f5f..95cfe7d2b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -223,7 +223,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3224,16 +3224,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index bb9d76306b..5781a4b995 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -210,6 +210,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -282,7 +283,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -341,7 +342,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -355,8 +356,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bd068bba05..f542b43549 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 5250ae7f54..1e5e2abcda 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 213eabfbb9..5e9d226e54 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -785,14 +785,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -819,18 +819,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..fda0eacf79 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -395,32 +396,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1628,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1657,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1741,8 +1748,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1789,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1852,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1881,28 +1869,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1931,8 +1900,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3046,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3185,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3257,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3413,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3424,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3697,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3709,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3796,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1d503e7e01..1d4611fb94 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1376,6 +1376,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1398,27 +1400,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 69e0fb98f5..485a52b04b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5139,7 +5139,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5191,7 +5191,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5270,7 +5270,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5318,7 +5318,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5379,6 +5379,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5387,7 +5388,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5456,7 +5457,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00dc0f2403..7c439a3cfb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..3d2de0ae1b 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 633e7671b3..080680ecd0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -965,37 +968,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1051,11 +1023,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1175,14 +1152,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6bda383bea..99c8d4f611 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 21e642a64c..aaff24256e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,10 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v23-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (5.5K, ../../CA+HiwqGdtO_FmS2AJtCWXbsKW6QhL9bkzXGKgYWXgBimUEa+6w@mail.gmail.com/5-v23-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From 32a23485a3a4001dd80cd1b13d61ae9bac6ecb87 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v23 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/nodes/readfuncs.c          |  4 ++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 src/include/nodes/meson.build          |  1 +
 9 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..e5c993c90d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -230,6 +230,10 @@ _readBitmapset(void)
 		result = bms_add_member(result, val);
 	}
 
+	/* XXX maybe do `result = makeNode(Bitmapset);` at the top? */
+	if (result)
+		result->type = T_Bitmapset;
+
 	return result;
 }
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b7df232081..94701af8e1 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -19,6 +19,7 @@ node_support_input_i = [
   'nodes/supportnodes.h',
   'nodes/value.h',
   'utils/rel.h',
+  'nodes/bitmapset.h',
 ]
 
 node_support_input = []
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-03 23:46  Ian Lawrence Barwick <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Ian Lawrence Barwick @ 2022-11-03 23:46 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

2022年10月15日(土) 15:01 Amit Langote <[email protected]>:
>
> On Fri, Oct 7, 2022 at 4:31 PM Amit Langote <[email protected]> wrote:
> > On Fri, Oct 7, 2022 at 3:49 PM Amit Langote <[email protected]> wrote:
> > > On Fri, Oct 7, 2022 at 1:25 PM Amit Langote <[email protected]> wrote:
> > > > On Fri, Oct 7, 2022 at 10:04 AM Amit Langote <[email protected]> wrote:
> > > > > On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> > > > > > Actually, List of Bitmapsets turned out to be something that doesn't
> > > > > > just-work with our Node infrastructure, which I found out thanks to
> > > > > > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > > > > > first-class support for copy/equal/write/read support for Bitmapsets,
> > > > > > such that writeNode() can write appropriately labeled versions of them
> > > > > > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > > > > > didn't actually go ahead and make *all* Bitmapsets in the plan trees
> > > > > > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > > > > > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > > > > > just use *_NODE_FIELD().
> > > > >
> > > > > All meson builds on the cfbot machines seem to have failed, maybe
> > > > > because I didn't update src/include/nodes/meson.build to add
> > > > > 'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
> > > > > an updated version assuming that's the problem.  (Will set up meson
> > > > > builds on my machine to avoid this in the future.)
> > > >
> > > > And... noticed that a postgres_fdw test failed, because
> > > > _readBitmapset() not having been changed to set NodeTag would
> > > > "corrupt" any Bitmapsets that were created with it set.
> > >
> > > Broke the other cases while fixing the above.  Attaching a new version
> > > again.  In the latest version, I'm setting Bitmapset.type by hand with
> > > an XXX comment nearby saying that it would be nice to change that to
> > > makeNode(Bitmapset), which I know sounds pretty ad-hoc.
> >
> > Sorry, I attached the wrong patches with the last email.  The
> > "correct" v22 attached this time.
>
> Rebased over c037471832.

This entry was marked as "Needs review" in the CommitFest app but cfbot
reports the patch no longer applies.

We've marked it as "Waiting on Author". As CommitFest 2022-11 is
currently underway, this would be an excellent time update the patch.

Once you think the patchset is ready for review again, you (or any
interested party) can  move the patch entry forward by visiting

    https://commitfest.postgresql.org/40/3224/

and changing the status to "Needs review".


Thanks

Ian Barwick





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-07 07:03  Amit Langote <[email protected]>
  parent: Ian Lawrence Barwick <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-11-07 07:03 UTC (permalink / raw)
  To: Ian Lawrence Barwick <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Nov 4, 2022 at 8:46 AM Ian Lawrence Barwick <[email protected]> wrote:
> 2022年10月15日(土) 15:01 Amit Langote <[email protected]>:
> > On Fri, Oct 7, 2022 at 4:31 PM Amit Langote <[email protected]> wrote:
> > > On Fri, Oct 7, 2022 at 3:49 PM Amit Langote <[email protected]> wrote:
> > > > On Fri, Oct 7, 2022 at 1:25 PM Amit Langote <[email protected]> wrote:
> > > > > On Fri, Oct 7, 2022 at 10:04 AM Amit Langote <[email protected]> wrote:
> > > > > > On Thu, Oct 6, 2022 at 10:29 PM Amit Langote <[email protected]> wrote:
> > > > > > > Actually, List of Bitmapsets turned out to be something that doesn't
> > > > > > > just-work with our Node infrastructure, which I found out thanks to
> > > > > > > -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> > > > > > > first-class support for copy/equal/write/read support for Bitmapsets,
> > > > > > > such that writeNode() can write appropriately labeled versions of them
> > > > > > > and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> > > > > > > didn't actually go ahead and make *all* Bitmapsets in the plan trees
> > > > > > > to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> > > > > > > to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> > > > > > > just use *_NODE_FIELD().
> > > > > >
> > > > > > All meson builds on the cfbot machines seem to have failed, maybe
> > > > > > because I didn't update src/include/nodes/meson.build to add
> > > > > > 'nodes/bitmapset.h' to the `node_support_input_i` collection.  Here's
> > > > > > an updated version assuming that's the problem.  (Will set up meson
> > > > > > builds on my machine to avoid this in the future.)
> > > > >
> > > > > And... noticed that a postgres_fdw test failed, because
> > > > > _readBitmapset() not having been changed to set NodeTag would
> > > > > "corrupt" any Bitmapsets that were created with it set.
> > > >
> > > > Broke the other cases while fixing the above.  Attaching a new version
> > > > again.  In the latest version, I'm setting Bitmapset.type by hand with
> > > > an XXX comment nearby saying that it would be nice to change that to
> > > > makeNode(Bitmapset), which I know sounds pretty ad-hoc.
> > >
> > > Sorry, I attached the wrong patches with the last email.  The
> > > "correct" v22 attached this time.
> >
> > Rebased over c037471832.
>
> This entry was marked as "Needs review" in the CommitFest app but cfbot
> reports the patch no longer applies.
>
> We've marked it as "Waiting on Author". As CommitFest 2022-11 is
> currently underway, this would be an excellent time update the patch.

Thanks for the heads up.

> Once you think the patchset is ready for review again, you (or any
> interested party) can  move the patch entry forward by visiting
>
>     https://commitfest.postgresql.org/40/3224/
>
> and changing the status to "Needs review".

Rebased patch attached and done.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v24-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch (5.5K, ../../CA+HiwqEYCLRZ2Boq_uK0pjLn_9b8XL-LmwKj7HN5kJOivUkYLg@mail.gmail.com/2-v24-0003-Allow-adding-Bitmapsets-as-Nodes-into-plan-trees.patch)
  download | inline diff:
From 6f3d9cd2119ae3074d838558158432185c7cdb0d Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Thu, 6 Oct 2022 17:31:37 +0900
Subject: [PATCH v24 3/4] Allow adding Bitmapsets as Nodes into plan trees

Note that this only adds some infrastructure bits and none of the
existing bitmapsets that are added to plan trees have been changed
to instead add the Node version.  So, the plan trees, or really the
bitmapsets contained in them, look the same as before as far as
Node write/read functionality is concerned.

This is needed, because it is not currently possible to write and
then read back Bitmapsets that are not direct members of write/read
capable Nodes; for example, if one needs to add a List of Bitmapsets
to a plan tree.  The most straightforward way to do that is to make
Bitmapsets be written with outNode() and read with nodeRead().
---
 src/backend/nodes/Makefile             |  3 ++-
 src/backend/nodes/copyfuncs.c          | 11 +++++++++++
 src/backend/nodes/equalfuncs.c         |  6 ++++++
 src/backend/nodes/gen_node_support.pl  |  1 +
 src/backend/nodes/outfuncs.c           | 11 +++++++++++
 src/backend/nodes/readfuncs.c          |  4 ++++
 src/backend/optimizer/prep/preptlist.c |  1 -
 src/include/nodes/bitmapset.h          |  5 +++++
 src/include/nodes/meson.build          |  1 +
 9 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 7450e191ee..da5307771b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -57,7 +57,8 @@ node_headers = \
 	nodes/replnodes.h \
 	nodes/supportnodes.h \
 	nodes/value.h \
-	utils/rel.h
+	utils/rel.h \
+	nodes/bitmapset.h
 
 # see also catalog/Makefile for an explanation of these make rules
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e76fda8eba..1482019327 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -160,6 +160,17 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* Custom copy routine for Node bitmapsets */
+static Bitmapset *
+_copyBitmapset(const Bitmapset *from)
+{
+	Bitmapset *newnode = bms_copy(from);
+
+	newnode->type = T_Bitmapset;
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0373aa30fe..e8706c461a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -210,6 +210,12 @@ _equalList(const List *a, const List *b)
 	return true;
 }
 
+/* Custom equal routine for Node bitmapsets */
+static bool
+_equalBitmapset(const Bitmapset *a, const Bitmapset *b)
+{
+	return bms_equal(a, b);
+}
 
 /*
  * equal
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81b8c184a9..ccb5aff874 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -71,6 +71,7 @@ my @all_input_files = qw(
   nodes/supportnodes.h
   nodes/value.h
   utils/rel.h
+  nodes/bitmapset.h
 );
 
 # Nodes from these input files are automatically treated as nodetag_only.
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b91e235423..d3beb907ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -328,6 +328,17 @@ outBitmapset(StringInfo str, const Bitmapset *bms)
 	appendStringInfoChar(str, ')');
 }
 
+/* Custom write routine for Node bitmapsets */
+static void
+_outBitmapset(StringInfo str, const Bitmapset *bms)
+{
+	Assert(IsA(bms, Bitmapset));
+	WRITE_NODE_TYPE("BITMAPSET");
+
+	outBitmapset(str, bms);
+}
+
+
 /*
  * Print the value of a Datum given its type.
  */
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 75bf11c741..e5c993c90d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -230,6 +230,10 @@ _readBitmapset(void)
 		result = bms_add_member(result, val);
 	}
 
+	/* XXX maybe do `result = makeNode(Bitmapset);` at the top? */
+	if (result)
+		result->type = T_Bitmapset;
+
 	return result;
 }
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..e5c1103316 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -337,7 +337,6 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
-
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 75b5ce1a8e..9046ca177f 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -20,6 +20,8 @@
 #ifndef BITMAPSET_H
 #define BITMAPSET_H
 
+#include "nodes/nodes.h"
+
 /*
  * Forward decl to save including pg_list.h
  */
@@ -48,6 +50,9 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
 
 typedef struct Bitmapset
 {
+	pg_node_attr(custom_copy_equal, custom_read_write)
+
+	NodeTag		type;
 	int			nwords;			/* number of words in array */
 	bitmapword	words[FLEXIBLE_ARRAY_MEMBER];	/* really [nwords] */
 } Bitmapset;
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b7df232081..94701af8e1 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -19,6 +19,7 @@ node_support_input_i = [
   'nodes/supportnodes.h',
   'nodes/value.h',
   'utils/rel.h',
+  'nodes/bitmapset.h',
 ]
 
 node_support_input = []
-- 
2.35.3



  [application/octet-stream] v24-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch (121.0K, ../../CA+HiwqEYCLRZ2Boq_uK0pjLn_9b8XL-LmwKj7HN5kJOivUkYLg@mail.gmail.com/3-v24-0002-Do-not-add-hidden-OLD-NEW-RTEs-to-stored-view-ru.patch)
  download | inline diff:
From 45bb5109569b4d467df380e840b7d2d94e764938 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 20 Aug 2021 20:05:26 +0900
Subject: [PATCH v24 2/4] Do not add hidden OLD/NEW RTEs to stored view rule
 actions

They were being added so that querying a view relation would
correctly check its permissions and lock it during execution, along
with the table(s) mentioned in the view query.

The commit that introduced RTEPermissionInfo nodes into query
processing to handle permission checking makes it redundant to
have an RTE for that purpose.  Though an RTE still must be present
for the view relations mentioned in the query to be locked during
execution and for them to be remembered in PlannedStmt.relationOids,
so this commit teaches the rewriter to add a copy of the original
view RTE.

As this changes the shape of the view queries stored in the catalog
due to hidden OLD/NEW RTEs no longer being present in the range table,
a bunch of regression tests that display those queries now display
them such that columns are longer qualified with their relation's name
in some cases, like when only one relation is mentioned in the view's
query.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  16 +-
 src/backend/commands/lockcmds.c               |   9 -
 src/backend/commands/view.c                   |  78 --
 src/backend/rewrite/rewriteDefine.c           |   7 -
 src/backend/rewrite/rewriteHandler.c          |  33 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/test/regress/expected/aggregates.out      |  26 +-
 src/test/regress/expected/alter_table.out     |  16 +-
 .../regress/expected/collate.icu.utf8.out     |  24 +-
 .../regress/expected/collate.linux.utf8.out   |  24 +-
 src/test/regress/expected/collate.out         |  26 +-
 src/test/regress/expected/compression.out     |   4 +-
 src/test/regress/expected/create_view.out     | 222 +++---
 src/test/regress/expected/expressions.out     |  24 +-
 src/test/regress/expected/groupingsets.out    |  20 +-
 src/test/regress/expected/limit.out           |  24 +-
 src/test/regress/expected/matview.out         |  24 +-
 src/test/regress/expected/polymorphism.out    |   8 +-
 src/test/regress/expected/rangefuncs.out      |  34 +-
 src/test/regress/expected/rules.out           | 744 +++++++++---------
 src/test/regress/expected/tablesample.out     |   4 +-
 src/test/regress/expected/triggers.out        |   4 +-
 src/test/regress/expected/updatable_views.out |  78 +-
 src/test/regress/expected/window.out          |  56 +-
 src/test/regress/expected/with.out            |  32 +-
 src/test/regress/expected/xml.out             |   6 +-
 src/test/regress/expected/xml_2.out           |   6 +-
 27 files changed, 737 insertions(+), 824 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 558e94b845..301d27aca5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r6.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r7 ON (((r6.c1 = r7.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -6555,10 +6555,10 @@ CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT foreign_tbl.a,
-    foreign_tbl.b
+ SELECT a,
+    b
    FROM foreign_tbl
-  WHERE foreign_tbl.a < foreign_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -6672,10 +6672,10 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT parent_tbl.a,
-    parent_tbl.b
+ SELECT a,
+    b
    FROM parent_tbl
-  WHERE parent_tbl.a < parent_tbl.b;
+  WHERE a < b;
 Options: check_option=cascaded
 
 EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..1d5f30443b 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -194,15 +194,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char		relkind = rte->relkind;
 			char	   *relname = get_rel_name(relid);
 
-			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
-			 */
-			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
-				continue;
-
 			/* Currently, we only allow plain tables or views to be locked. */
 			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
 				relkind != RELKIND_VIEW)
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f07ac2a9c..7e3d5e79bc 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -353,78 +353,6 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 	 */
 }
 
-/*---------------------------------------------------------------
- * UpdateRangeTableOfViewParse
- *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
- *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
- *---------------------------------------------------------------
- */
-static Query *
-UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
-{
-	Relation	viewRel;
-	List	   *new_rt;
-	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	ParseState *pstate;
-
-	/*
-	 * Make a copy of the given parsetree.  It's not so much that we don't
-	 * want to scribble on our input, it's that the parser has a bad habit of
-	 * outputting multiple links to the same subtree for constructs like
-	 * BETWEEN, and we mustn't have OffsetVarNodes increment the varno of a
-	 * Var node twice.  copyObject will expand any multiply-referenced subtree
-	 * into multiple copies.
-	 */
-	viewParse = copyObject(viewParse);
-
-	/* Create a dummy ParseState for addRangeTableEntryForRelation */
-	pstate = make_parsestate(NULL);
-
-	/* need to open the rel for addRangeTableEntryForRelation */
-	viewRel = relation_open(viewOid, AccessShareLock);
-
-	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
-	 */
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
-
-	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
-	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
-
-	relation_close(viewRel, AccessShareLock);
-
-	return viewParse;
-}
-
 /*
  * DefineView
  *		Execute a CREATE VIEW command.
@@ -587,12 +515,6 @@ DefineView(ViewStmt *stmt, const char *queryString,
 void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
-	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
-	 */
-	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
-
 	/*
 	 * Now create the rules associated with the view.
 	 */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 1cb8e1b441..555c103371 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -798,13 +798,6 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
  *		field to the given userid in all RTEPermissionInfos of the query.
- *
- * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fda0eacf79..77cd294e51 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1748,7 +1748,8 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte;
+	RangeTblEntry *rte,
+				  *subquery_rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1860,17 +1861,26 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	subquery_rte = rte;
 
-	rte->rtekind = RTE_SUBQUERY;
-	rte->subquery = rule_action;
-	rte->security_barrier = RelationIsSecurityView(relation);
+	/*
+	 * Before converting the RTE to become a SUBQUERY, store a copy for the
+	 * executor to be able to lock the view relation and for the planner to be
+	 * able to record the view relation OID in PlannedStmt.relationOids.
+	 */
+	rte = copyObject(rte);
+	parsetree->rtable = lappend(parsetree->rtable, rte);
+
+	subquery_rte->rtekind = RTE_SUBQUERY;
+	subquery_rte->subquery = rule_action;
+	subquery_rte->security_barrier = RelationIsSecurityView(relation);
 	/* Clear fields that should not be set in a subquery RTE */
-	rte->relid = InvalidOid;
-	rte->relkind = 0;
-	rte->rellockmode = 0;
-	rte->tablesample = NULL;
-	rte->perminfoindex = 0;
-	rte->inh = false;			/* must not be set for a subquery */
+	subquery_rte->relid = InvalidOid;
+	subquery_rte->relkind = 0;
+	subquery_rte->rellockmode = 0;
+	subquery_rte->tablesample = NULL;
+	subquery_rte->perminfoindex = 0;
+	subquery_rte->inh = false;			/* must not be set for a subquery */
 
 	return parsetree;
 }
@@ -1882,9 +1892,6 @@ ApplyRetrieveRule(Query *parsetree,
  * aggregate.  We leave it to the planner to detect that.
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
- * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 8dc1f0eccb..67dbfa55fd 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2228,7 +2228,7 @@ my %tests = (
 					   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2244,7 +2244,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_second AS\E
-			\n\s+\QSELECT matview.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2260,7 +2260,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_second WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_third AS\E
-			\n\s+\QSELECT matview_second.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_second\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2276,7 +2276,7 @@ my %tests = (
 						   SELECT * FROM dump_test.matview_third WITH NO DATA;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_fourth AS\E
-			\n\s+\QSELECT matview_third.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.matview_third\E
 			\n\s+\QWITH NO DATA;\E
 			/xm,
@@ -2294,7 +2294,7 @@ my %tests = (
 						   ALTER COLUMN col2 SET COMPRESSION lz4;',
 		regexp => qr/^
 			\QCREATE MATERIALIZED VIEW dump_test.matview_compression AS\E
-			\n\s+\QSELECT test_table.col2\E
+			\n\s+\QSELECT col2\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH NO DATA;\E
 			.*
@@ -3290,7 +3290,7 @@ my %tests = (
 		                   SELECT col1 FROM dump_test.test_table;',
 		regexp => qr/^
 			\QCREATE VIEW dump_test.test_view WITH (security_barrier='true') AS\E
-			\n\s+\QSELECT test_table.col1\E
+			\n\s+\QSELECT col1\E
 			\n\s+\QFROM dump_test.test_table\E
 			\n\s+\QWITH LOCAL CHECK OPTION;\E/xm,
 		like =>
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index fc2bd40be2..564a7ba1aa 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1623,7 +1623,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c) AS aggfns                                                                            +
+  SELECT aggfns(a, b, c) AS aggfns                                                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1675,7 +1675,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY (v.b + 1)) AS aggfns                                                         +
+  SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns                                                                 +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1691,7 +1691,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.a, v.c ORDER BY v.b) AS aggfns                                                               +
+  SELECT aggfns(a, a, c ORDER BY b) AS aggfns                                                                       +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -1707,7 +1707,7 @@ select * from agg_view1;
 select pg_get_viewdef('agg_view1'::regclass);
                                                    pg_get_viewdef                                                    
 ---------------------------------------------------------------------------------------------------------------------
-  SELECT aggfns(v.a, v.b, v.c ORDER BY v.c USING ~<~ NULLS LAST) AS aggfns                                          +
+  SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns                                                  +
     FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c);
 (1 row)
 
@@ -2192,15 +2192,15 @@ select ten,
   from tenk1
  group by ten order by ten;
 select pg_get_viewdef('aggordview1');
-                                                        pg_get_viewdef                                                         
--------------------------------------------------------------------------------------------------------------------------------
-  SELECT tenk1.ten,                                                                                                           +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50,                                  +
-     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px,+
-     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank              +
-    FROM tenk1                                                                                                                +
-   GROUP BY tenk1.ten                                                                                                         +
-   ORDER BY tenk1.ten;
+                                                  pg_get_viewdef                                                   
+-------------------------------------------------------------------------------------------------------------------
+  SELECT ten,                                                                                                     +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50,                            +
+     percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+
+     rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank                    +
+    FROM tenk1                                                                                                    +
+   GROUP BY ten                                                                                                   +
+   ORDER BY ten;
 (1 row)
 
 select * from aggordview1 order by ten;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 346f594ad0..ecf4f65c12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2479,8 +2479,8 @@ create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1;
  id     | integer |           |          |         | plain    | 
  stuff  | text    |           |          |         | extended | 
 View definition:
- SELECT bt.id,
-    bt.stuff
+ SELECT id,
+    stuff
    FROM at_base_table bt;
 
 \d+ at_view_2
@@ -2491,8 +2491,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
@@ -2518,8 +2518,8 @@ create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt;
  stuff  | text    |           |          |         | extended | 
  more   | integer |           |          |         | plain    | 
 View definition:
- SELECT bt.id,
-    bt.stuff,
+ SELECT id,
+    stuff,
     2 + 2 AS more
    FROM at_base_table bt;
 
@@ -2531,8 +2531,8 @@ View definition:
  stuff  | text    |           |          |         | extended | 
  j      | json    |           |          |         | extended | 
 View definition:
- SELECT v1.id,
-    v1.stuff,
+ SELECT id,
+    stuff,
     to_json(v1.*) AS j
    FROM at_view_1 v1;
 
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..4354dc07b8 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -446,18 +446,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index f2d0eb94f2..2098696ec2 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -483,18 +483,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                             view_definition                              
-------------+--------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                +
-            |     collate_test1.b                                                     +
-            |    FROM collate_test1                                                   +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                               +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower+
+ table_name |              view_definition               
+------------+--------------------------------------------
+ collview1  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                +
+            |     b                                     +
+            |    FROM collate_test1                     +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                +
+            |     lower(((x || x) COLLATE "C")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index 246832575c..0649564485 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -194,18 +194,18 @@ CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
 CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10;
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'collview%' ORDER BY 1;
- table_name |                               view_definition                                
-------------+------------------------------------------------------------------------------
- collview1  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text);
- collview2  |  SELECT collate_test1.a,                                                    +
-            |     collate_test1.b                                                         +
-            |    FROM collate_test1                                                       +
-            |   ORDER BY (collate_test1.b COLLATE "C");
- collview3  |  SELECT collate_test10.a,                                                   +
-            |     lower(((collate_test10.x || collate_test10.x) COLLATE "POSIX")) AS lower+
+ table_name |                view_definition                 
+------------+------------------------------------------------
+ collview1  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   WHERE ((b COLLATE "C") >= 'bbc'::text);
+ collview2  |  SELECT a,                                    +
+            |     b                                         +
+            |    FROM collate_test1                         +
+            |   ORDER BY (b COLLATE "C");
+ collview3  |  SELECT a,                                    +
+            |     lower(((x || x) COLLATE "POSIX")) AS lower+
             |    FROM collate_test10;
 (3 rows)
 
@@ -698,7 +698,7 @@ SELECT c1+1 AS c1p FROM
 --------+---------+-----------+----------+---------+---------+-------------
  c1p    | integer |           |          |         | plain   | 
 View definition:
- SELECT ss.c1 + 1 AS c1p
+ SELECT c1 + 1 AS c1p
    FROM ( SELECT 4 AS c1) ss;
 
 -- Check conflicting or redundant options in CREATE COLLATION
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 4c997e2602..e06ac93a36 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -187,7 +187,7 @@ CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended |             |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 SELECT pg_column_compression(f1) FROM cmdata1;
@@ -274,7 +274,7 @@ ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  x      | text |           |          |         | extended | lz4         |              | 
 View definition:
- SELECT cmdata1.f1 AS x
+ SELECT f1 AS x
    FROM cmdata1;
 
 -- test alter compression method for partitioned tables
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f9bbad00df..880caca491 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -395,10 +395,10 @@ CREATE VIEW tt1 AS
  c      | numeric              |           |          |         | main     | 
  d      | character varying(4) |           |          |         | extended | 
 View definition:
- SELECT vv.a,
-    vv.b,
-    vv.c,
-    vv.d
+ SELECT a,
+    b,
+    c,
+    d
    FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
 
 SELECT * FROM tt1;
@@ -440,9 +440,9 @@ CREATE VIEW aliased_view_4 AS
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -456,9 +456,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tx1
@@ -472,9 +472,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM tx1 a2
@@ -488,9 +488,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -505,9 +505,9 @@ ALTER TABLE tx1 RENAME TO a1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -521,9 +521,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -537,9 +537,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.f1,
-    tt1.f2,
-    tt1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM tt1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2
@@ -553,9 +553,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 tt1_1
@@ -570,9 +570,9 @@ ALTER TABLE tt1 RENAME TO a2;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1
@@ -586,9 +586,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM a1 a1_1
@@ -602,9 +602,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM a1 a2_1
@@ -618,9 +618,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -635,9 +635,9 @@ ALTER TABLE a1 RENAME TO tt1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -651,9 +651,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -667,9 +667,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a2.f1,
-    a2.f2,
-    a2.f3
+ SELECT f1,
+    f2,
+    f3
    FROM a2
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2_1
@@ -683,9 +683,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM a2
@@ -701,9 +701,9 @@ ALTER TABLE tx1 SET SCHEMA temp_view_test;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -717,9 +717,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -733,9 +733,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -749,9 +749,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tt1.y1,
-    tt1.f2,
-    tt1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM temp_view_test.tt1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1
@@ -768,9 +768,9 @@ ALTER TABLE tmp1 RENAME TO tx1;
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -784,9 +784,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT a1.f1,
-    a1.f2,
-    a1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1 a1
   WHERE (EXISTS ( SELECT 1
            FROM tt1
@@ -800,9 +800,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.f1,
-    tx1.f2,
-    tx1.f3
+ SELECT f1,
+    f2,
+    f3
    FROM temp_view_test.tx1
   WHERE (EXISTS ( SELECT 1
            FROM tt1 a2
@@ -816,9 +816,9 @@ View definition:
  f2     | integer |           |          |         | plain    | 
  f3     | text    |           |          |         | extended | 
 View definition:
- SELECT tx1.y1,
-    tx1.f2,
-    tx1.f3
+ SELECT y1,
+    f2,
+    f3
    FROM tx1
   WHERE (EXISTS ( SELECT 1
            FROM temp_view_test.tx1 tx1_1
@@ -1305,10 +1305,10 @@ select pg_get_viewdef('v1', true);
 select pg_get_viewdef('v4', true);
  pg_get_viewdef 
 ----------------
-  SELECT v1.b, +
-     v1.c,     +
-     v1.x AS a,+
-     v1.ax     +
+  SELECT b,    +
+     c,        +
+     x AS a,   +
+     ax        +
     FROM v1;
 (1 row)
 
@@ -1585,9 +1585,9 @@ create view tt14v as select t.* from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1623,11 +1623,11 @@ returning pg_describe_object(classid, objid, objsubid) as obj,
 alter table tt14t drop column f3;
 -- column f3 is still in the view, sort of ...
 select pg_get_viewdef('tt14v', true);
-         pg_get_viewdef          
----------------------------------
-  SELECT t.f1,                  +
-     t."?dropped?column?" AS f3,+
-     t.f4                       +
+        pg_get_viewdef         
+-------------------------------
+  SELECT f1,                  +
+     "?dropped?column?" AS f3,+
+     f4                       +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1675,9 +1675,9 @@ alter table tt14t alter column f4 type integer using f4::integer;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f3,                     +
-     t.f4                      +
+  SELECT f1,                   +
+     f3,                       +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1697,8 +1697,8 @@ create view tt14v as select t.f1, t.f4 from tt14f() t;
 select pg_get_viewdef('tt14v', true);
          pg_get_viewdef         
 --------------------------------
-  SELECT t.f1,                 +
-     t.f4                      +
+  SELECT f1,                   +
+     f4                        +
     FROM tt14f() t(f1, f3, f4);
 (1 row)
 
@@ -1712,8 +1712,8 @@ alter table tt14t drop column f3;  -- ok
 select pg_get_viewdef('tt14v', true);
        pg_get_viewdef       
 ----------------------------
-  SELECT t.f1,             +
-     t.f4                  +
+  SELECT f1,               +
+     f4                    +
     FROM tt14f() t(f1, f4);
 (1 row)
 
@@ -1806,8 +1806,8 @@ select * from tt17v;
 select pg_get_viewdef('tt17v', true);
                pg_get_viewdef                
 ---------------------------------------------
-  SELECT i.q1,                              +
-     i.q2                                   +
+  SELECT q1,                                +
+     q2                                     +
     FROM int8_tbl i                         +
    WHERE (i.* IN ( VALUES (i.*::int8_tbl)));
 (1 row)
@@ -2132,7 +2132,7 @@ select pg_get_viewdef('tt25v', true);
   WITH cte AS MATERIALIZED (           +
           SELECT pg_get_keywords() AS k+
          )                             +
-  SELECT (cte.k).word AS word          +
+  SELECT (k).word AS word              +
     FROM cte;
 (1 row)
 
@@ -2184,19 +2184,19 @@ select x + y + z as c1,
        (x,y) <= ANY (values(1,2),(3,4)) as c11
 from (values(1,2,3)) v(x,y,z);
 select pg_get_viewdef('tt26v', true);
-                     pg_get_viewdef                     
---------------------------------------------------------
-  SELECT v.x + v.y + v.z AS c1,                        +
-     v.x * v.y + v.z AS c2,                            +
-     v.x + v.y * v.z AS c3,                            +
-     (v.x + v.y) * v.z AS c4,                          +
-     v.x * (v.y + v.z) AS c5,                          +
-     v.x + (v.y + v.z) AS c6,                          +
-     v.x + (v.y # v.z) AS c7,                          +
-     v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8,     +
-     v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9,   +
-     ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
-     ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT x + y + z AS c1,                          +
+     x * y + z AS c2,                              +
+     x + y * z AS c3,                              +
+     (x + y) * z AS c4,                            +
+     x * (y + z) AS c5,                            +
+     x + (y + z) AS c6,                            +
+     x + (y # z) AS c7,                            +
+     x > y AND (y > z OR x > z) AS c8,             +
+     x > y OR y > z AND NOT x > z AS c9,           +
+     ((x, y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+
+     ((x, y) <= ANY ( VALUES (1,2), (3,4))) AS c11 +
     FROM ( VALUES (1,2,3)) v(x, y, z);
 (1 row)
 
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5bf39fd9aa..0ab6a71894 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -108,12 +108,12 @@ create view numeric_view as
  f2164  | numeric(16,4) |           |          |         | main    | 
  f2n    | numeric       |           |          |         | main    | 
 View definition:
- SELECT numeric_tbl.f1,
-    numeric_tbl.f1::numeric(16,4) AS f1164,
-    numeric_tbl.f1::numeric AS f1n,
-    numeric_tbl.f2,
-    numeric_tbl.f2::numeric(16,4) AS f2164,
-    numeric_tbl.f2 AS f2n
+ SELECT f1,
+    f1::numeric(16,4) AS f1164,
+    f1::numeric AS f1n,
+    f2,
+    f2::numeric(16,4) AS f2164,
+    f2 AS f2n
    FROM numeric_tbl;
 
 explain (verbose, costs off) select * from numeric_view;
@@ -142,12 +142,12 @@ create view bpchar_view as
  f214   | character(14) |           |          |         | extended | 
  f2n    | bpchar        |           |          |         | extended | 
 View definition:
- SELECT bpchar_tbl.f1,
-    bpchar_tbl.f1::character(14) AS f114,
-    bpchar_tbl.f1::bpchar AS f1n,
-    bpchar_tbl.f2,
-    bpchar_tbl.f2::character(14) AS f214,
-    bpchar_tbl.f2 AS f2n
+ SELECT f1,
+    f1::character(14) AS f114,
+    f1::bpchar AS f1n,
+    f2,
+    f2::character(14) AS f214,
+    f2 AS f2n
    FROM bpchar_tbl;
 
 explain (verbose, costs off) select * from bpchar_view
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
index fcad5c4093..8e75bfe92a 100644
--- a/src/test/regress/expected/groupingsets.out
+++ b/src/test/regress/expected/groupingsets.out
@@ -570,16 +570,16 @@ CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
   from gstest2 group by rollup ((a,b,c),(c,d));
 NOTICE:  view "gstest_view" will be a temporary view
 select pg_get_viewdef('gstest_view'::regclass, true);
-                                pg_get_viewdef                                 
--------------------------------------------------------------------------------
-  SELECT gstest2.a,                                                           +
-     gstest2.b,                                                               +
-     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
-     sum(gstest2.c) AS sum,                                                   +
-     count(*) AS count,                                                       +
-     max(gstest2.c) AS max                                                    +
-    FROM gstest2                                                              +
-   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+            pg_get_viewdef             
+---------------------------------------
+  SELECT a,                           +
+     b,                               +
+     GROUPING(a, b) AS "grouping",    +
+     sum(c) AS sum,                   +
+     count(*) AS count,               +
+     max(c) AS max                    +
+    FROM gstest2                      +
+   GROUP BY ROLLUP((a, b, c), (c, d));
 (1 row)
 
 -- Nested queries with 3 or more levels of nesting
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 8a98bbea8e..a2cd0f9f5b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -638,10 +638,10 @@ CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  FETCH FIRST 5 ROWS WITH TIES;
 
@@ -653,10 +653,10 @@ CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  OFFSET 10
  LIMIT 5;
 
@@ -671,10 +671,10 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  FETCH FIRST (NULL::integer + 1) ROWS WITH TIES;
 
 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
@@ -685,10 +685,10 @@ CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995
 ----------+---------+-----------+----------+---------+---------+-------------
  thousand | integer |           |          |         | plain   | 
 View definition:
- SELECT onek.thousand
+ SELECT thousand
    FROM onek
-  WHERE onek.thousand < 995
-  ORDER BY onek.thousand
+  WHERE thousand < 995
+  ORDER BY thousand
  LIMIT ALL;
 
 -- leave these views
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index c109d97635..87b6e569a5 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -100,10 +100,10 @@ CREATE INDEX mvtest_aa ON mvtest_bb (grandtot);
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvm
                            Materialized view "public.mvtest_tvm"
@@ -112,10 +112,10 @@ View definition:
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 \d+ mvtest_tvvm
                            Materialized view "public.mvtest_tvvm"
@@ -123,7 +123,7 @@ View definition:
 ----------+---------+-----------+----------+---------+---------+--------------+-------------
  grandtot | numeric |           |          |         | main    |              | 
 View definition:
- SELECT mvtest_tvv.grandtot
+ SELECT grandtot
    FROM mvtest_tvv;
 
 \d+ mvtest_bb
@@ -134,7 +134,7 @@ View definition:
 Indexes:
     "mvtest_aa" btree (grandtot)
 View definition:
- SELECT mvtest_tvvmv.grandtot
+ SELECT grandtot
    FROM mvtest_tvvmv;
 
 -- test schema behavior
@@ -150,7 +150,7 @@ Indexes:
     "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric))
     "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric
 View definition:
- SELECT sum(mvtest_tvm.totamt) AS grandtot
+ SELECT sum(totamt) AS grandtot
    FROM mvtest_mvschema.mvtest_tvm;
 
 SET search_path = mvtest_mvschema, public;
@@ -161,10 +161,10 @@ SET search_path = mvtest_mvschema, public;
  type   | text    |           |          |         | extended |              | 
  totamt | numeric |           |          |         | main     |              | 
 View definition:
- SELECT mvtest_tv.type,
-    mvtest_tv.totamt
+ SELECT type,
+    totamt
    FROM mvtest_tv
-  ORDER BY mvtest_tv.type;
+  ORDER BY type;
 
 -- modify the underlying table data
 INSERT INTO mvtest_t VALUES (6, 'z', 13);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1cd558d668..bf08e40ed8 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1801,10 +1801,10 @@ select * from dfview;
  c3     | bigint |           |          |         | plain   | 
  c4     | bigint |           |          |         | plain   | 
 View definition:
- SELECT int8_tbl.q1,
-    int8_tbl.q2,
-    dfunc(int8_tbl.q1, int8_tbl.q2, flag => int8_tbl.q1 > int8_tbl.q2) AS c3,
-    dfunc(int8_tbl.q1, flag => int8_tbl.q1 < int8_tbl.q2, b => int8_tbl.q2) AS c4
+ SELECT q1,
+    q2,
+    dfunc(q1, q2, flag => q1 > q2) AS c3,
+    dfunc(q1, flag => q1 < q2, b => q2) AS c4
    FROM int8_tbl;
 
 drop view dfview;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e2e62db6a2..fbb840e848 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -149,9 +149,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -167,9 +167,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                        definition                                       
 ----------------------------------------------------------------------------------------
-  SELECT z.a,                                                                          +
-     z.b,                                                                              +
-     z.c                                                                               +
+  SELECT a,                                                                            +
+     b,                                                                                +
+     c                                                                                 +
     FROM UNNEST(ARRAY[10, 20], ARRAY['foo'::text, 'bar'::text], ARRAY[1.0]) z(a, b, c);
 (1 row)
 
@@ -185,9 +185,9 @@ select * from vw_ord;
 select definition from pg_views where viewname='vw_ord';
                                                       definition                                                      
 ----------------------------------------------------------------------------------------------------------------------
-  SELECT z.a,                                                                                                        +
-     z.b,                                                                                                            +
-     z.c                                                                                                             +
+  SELECT a,                                                                                                          +
+     b,                                                                                                              +
+     c                                                                                                               +
     FROM ROWS FROM(unnest(ARRAY[10, 20]), unnest(ARRAY['foo'::text, 'bar'::text]), generate_series(1, 2)) z(a, b, c);
 (1 row)
 
@@ -669,14 +669,14 @@ select * from vw_rngfunc;
 select pg_get_viewdef('vw_rngfunc');
                                                                                 pg_get_viewdef                                                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  SELECT t1.a,                                                                                                                                                               +
-     t1.b,                                                                                                                                                                   +
-     t1.c,                                                                                                                                                                   +
-     t1.d,                                                                                                                                                                   +
-     t1.e,                                                                                                                                                                   +
-     t1.f,                                                                                                                                                                   +
-     t1.g,                                                                                                                                                                   +
-     t1.n                                                                                                                                                                    +
+  SELECT a,                                                                                                                                                                  +
+     b,                                                                                                                                                                      +
+     c,                                                                                                                                                                      +
+     d,                                                                                                                                                                      +
+     e,                                                                                                                                                                      +
+     f,                                                                                                                                                                      +
+     g,                                                                                                                                                                      +
+     n                                                                                                                                                                       +
     FROM ROWS FROM(getrngfunc9(1), getrngfunc7(1) AS (rngfuncid integer, rngfuncsubid integer, rngfuncname text), getrngfunc1(1)) WITH ORDINALITY t1(a, b, c, d, e, f, g, n);
 (1 row)
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 624d0e5aae..1a5c3c063d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1302,58 +1302,58 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
-pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
-    pg_get_backend_memory_contexts.ident,
-    pg_get_backend_memory_contexts.parent,
-    pg_get_backend_memory_contexts.level,
-    pg_get_backend_memory_contexts.total_bytes,
-    pg_get_backend_memory_contexts.total_nblocks,
-    pg_get_backend_memory_contexts.free_bytes,
-    pg_get_backend_memory_contexts.free_chunks,
-    pg_get_backend_memory_contexts.used_bytes
+pg_backend_memory_contexts| SELECT name,
+    ident,
+    parent,
+    level,
+    total_bytes,
+    total_nblocks,
+    free_bytes,
+    free_chunks,
+    used_bytes
    FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
-pg_config| SELECT pg_config.name,
-    pg_config.setting
+pg_config| SELECT name,
+    setting
    FROM pg_config() pg_config(name, setting);
-pg_cursors| SELECT c.name,
-    c.statement,
-    c.is_holdable,
-    c.is_binary,
-    c.is_scrollable,
-    c.creation_time
+pg_cursors| SELECT name,
+    statement,
+    is_holdable,
+    is_binary,
+    is_scrollable,
+    creation_time
    FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
-pg_file_settings| SELECT a.sourcefile,
-    a.sourceline,
-    a.seqno,
-    a.name,
-    a.setting,
-    a.applied,
-    a.error
+pg_file_settings| SELECT sourcefile,
+    sourceline,
+    seqno,
+    name,
+    setting,
+    applied,
+    error
    FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
-pg_group| SELECT pg_authid.rolname AS groname,
-    pg_authid.oid AS grosysid,
+pg_group| SELECT rolname AS groname,
+    oid AS grosysid,
     ARRAY( SELECT pg_auth_members.member
            FROM pg_auth_members
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
-  WHERE (NOT pg_authid.rolcanlogin);
-pg_hba_file_rules| SELECT a.rule_number,
-    a.line_number,
-    a.type,
-    a.database,
-    a.user_name,
-    a.address,
-    a.netmask,
-    a.auth_method,
-    a.options,
-    a.error
+  WHERE (NOT rolcanlogin);
+pg_hba_file_rules| SELECT rule_number,
+    line_number,
+    type,
+    database,
+    user_name,
+    address,
+    netmask,
+    auth_method,
+    options,
+    error
    FROM pg_hba_file_rules() a(rule_number, line_number, type, database, user_name, address, netmask, auth_method, options, error);
-pg_ident_file_mappings| SELECT a.map_number,
-    a.line_number,
-    a.map_name,
-    a.sys_name,
-    a.pg_username,
-    a.error
+pg_ident_file_mappings| SELECT map_number,
+    line_number,
+    map_name,
+    sys_name,
+    pg_username,
+    error
    FROM pg_ident_file_mappings() a(map_number, line_number, map_name, sys_name, pg_username, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
@@ -1366,22 +1366,22 @@ pg_indexes| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace)))
   WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"])));
-pg_locks| SELECT l.locktype,
-    l.database,
-    l.relation,
-    l.page,
-    l.tuple,
-    l.virtualxid,
-    l.transactionid,
-    l.classid,
-    l.objid,
-    l.objsubid,
-    l.virtualtransaction,
-    l.pid,
-    l.mode,
-    l.granted,
-    l.fastpath,
-    l.waitstart
+pg_locks| SELECT locktype,
+    database,
+    relation,
+    page,
+    tuple,
+    virtualxid,
+    transactionid,
+    classid,
+    objid,
+    objsubid,
+    virtualtransaction,
+    pid,
+    mode,
+    granted,
+    fastpath,
+    waitstart
    FROM pg_lock_status() l(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid, virtualtransaction, pid, mode, granted, fastpath, waitstart);
 pg_matviews| SELECT n.nspname AS schemaname,
     c.relname AS matviewname,
@@ -1421,14 +1421,14 @@ pg_policies| SELECT n.nspname AS schemaname,
    FROM ((pg_policy pol
      JOIN pg_class c ON ((c.oid = pol.polrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)));
-pg_prepared_statements| SELECT p.name,
-    p.statement,
-    p.prepare_time,
-    p.parameter_types,
-    p.result_types,
-    p.from_sql,
-    p.generic_plans,
-    p.custom_plans
+pg_prepared_statements| SELECT name,
+    statement,
+    prepare_time,
+    parameter_types,
+    result_types,
+    from_sql,
+    generic_plans,
+    custom_plans
    FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql, generic_plans, custom_plans);
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
@@ -1450,10 +1450,10 @@ pg_publication_tables| SELECT p.pubname,
     (pg_class c
      JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.oid = gpt.relid);
-pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id,
-    pg_show_replication_origin_status.external_id,
-    pg_show_replication_origin_status.remote_lsn,
-    pg_show_replication_origin_status.local_lsn
+pg_replication_origin_status| SELECT local_id,
+    external_id,
+    remote_lsn,
+    local_lsn
    FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn);
 pg_replication_slots| SELECT l.slot_name,
     l.plugin,
@@ -1699,23 +1699,23 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
-pg_settings| SELECT a.name,
-    a.setting,
-    a.unit,
-    a.category,
-    a.short_desc,
-    a.extra_desc,
-    a.context,
-    a.vartype,
-    a.source,
-    a.min_val,
-    a.max_val,
-    a.enumvals,
-    a.boot_val,
-    a.reset_val,
-    a.sourcefile,
-    a.sourceline,
-    a.pending_restart
+pg_settings| SELECT name,
+    setting,
+    unit,
+    category,
+    short_desc,
+    extra_desc,
+    context,
+    vartype,
+    source,
+    min_val,
+    max_val,
+    enumvals,
+    boot_val,
+    reset_val,
+    sourcefile,
+    sourceline,
+    pending_restart
    FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart);
 pg_shadow| SELECT pg_authid.rolname AS usename,
     pg_authid.oid AS usesysid,
@@ -1729,10 +1729,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
    FROM (pg_authid
      LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
   WHERE pg_authid.rolcanlogin;
-pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
-    pg_get_shmem_allocations.off,
-    pg_get_shmem_allocations.size,
-    pg_get_shmem_allocations.allocated_size
+pg_shmem_allocations| SELECT name,
+    off,
+    size,
+    allocated_size
    FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
 pg_stat_activity| SELECT s.datid,
     d.datname,
@@ -1803,13 +1803,13 @@ pg_stat_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_archiver| SELECT s.archived_count,
-    s.last_archived_wal,
-    s.last_archived_time,
-    s.failed_count,
-    s.last_failed_wal,
-    s.last_failed_time,
-    s.stats_reset
+pg_stat_archiver| SELECT archived_count,
+    last_archived_wal,
+    last_archived_time,
+    failed_count,
+    last_failed_wal,
+    last_failed_time,
+    stats_reset
    FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
 pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed,
     pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req,
@@ -1822,57 +1822,57 @@ pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints
     pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync,
     pg_stat_get_buf_alloc() AS buffers_alloc,
     pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
-pg_stat_database| SELECT d.oid AS datid,
-    d.datname,
+pg_stat_database| SELECT oid AS datid,
+    datname,
         CASE
-            WHEN (d.oid = (0)::oid) THEN 0
-            ELSE pg_stat_get_db_numbackends(d.oid)
+            WHEN (oid = (0)::oid) THEN 0
+            ELSE pg_stat_get_db_numbackends(oid)
         END AS numbackends,
-    pg_stat_get_db_xact_commit(d.oid) AS xact_commit,
-    pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback,
-    (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read,
-    pg_stat_get_db_blocks_hit(d.oid) AS blks_hit,
-    pg_stat_get_db_tuples_returned(d.oid) AS tup_returned,
-    pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched,
-    pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted,
-    pg_stat_get_db_tuples_updated(d.oid) AS tup_updated,
-    pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted,
-    pg_stat_get_db_conflict_all(d.oid) AS conflicts,
-    pg_stat_get_db_temp_files(d.oid) AS temp_files,
-    pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes,
-    pg_stat_get_db_deadlocks(d.oid) AS deadlocks,
-    pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures,
-    pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure,
-    pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time,
-    pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time,
-    pg_stat_get_db_session_time(d.oid) AS session_time,
-    pg_stat_get_db_active_time(d.oid) AS active_time,
-    pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time,
-    pg_stat_get_db_sessions(d.oid) AS sessions,
-    pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned,
-    pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal,
-    pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed,
-    pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset
+    pg_stat_get_db_xact_commit(oid) AS xact_commit,
+    pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
+    (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
+    pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    pg_stat_get_db_tuples_returned(oid) AS tup_returned,
+    pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
+    pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
+    pg_stat_get_db_tuples_updated(oid) AS tup_updated,
+    pg_stat_get_db_tuples_deleted(oid) AS tup_deleted,
+    pg_stat_get_db_conflict_all(oid) AS conflicts,
+    pg_stat_get_db_temp_files(oid) AS temp_files,
+    pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+    pg_stat_get_db_deadlocks(oid) AS deadlocks,
+    pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
+    pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
+    pg_stat_get_db_blk_read_time(oid) AS blk_read_time,
+    pg_stat_get_db_blk_write_time(oid) AS blk_write_time,
+    pg_stat_get_db_session_time(oid) AS session_time,
+    pg_stat_get_db_active_time(oid) AS active_time,
+    pg_stat_get_db_idle_in_transaction_time(oid) AS idle_in_transaction_time,
+    pg_stat_get_db_sessions(oid) AS sessions,
+    pg_stat_get_db_sessions_abandoned(oid) AS sessions_abandoned,
+    pg_stat_get_db_sessions_fatal(oid) AS sessions_fatal,
+    pg_stat_get_db_sessions_killed(oid) AS sessions_killed,
+    pg_stat_get_db_stat_reset_time(oid) AS stats_reset
    FROM ( SELECT 0 AS oid,
             NULL::name AS datname
         UNION ALL
          SELECT pg_database.oid,
             pg_database.datname
            FROM pg_database) d;
-pg_stat_database_conflicts| SELECT d.oid AS datid,
-    d.datname,
-    pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace,
-    pg_stat_get_db_conflict_lock(d.oid) AS confl_lock,
-    pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot,
-    pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
-    pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
+pg_stat_database_conflicts| SELECT oid AS datid,
+    datname,
+    pg_stat_get_db_conflict_tablespace(oid) AS confl_tablespace,
+    pg_stat_get_db_conflict_lock(oid) AS confl_lock,
+    pg_stat_get_db_conflict_snapshot(oid) AS confl_snapshot,
+    pg_stat_get_db_conflict_bufferpin(oid) AS confl_bufferpin,
+    pg_stat_get_db_conflict_startup_deadlock(oid) AS confl_deadlock
    FROM pg_database d;
-pg_stat_gssapi| SELECT s.pid,
-    s.gss_auth AS gss_authenticated,
-    s.gss_princ AS principal,
-    s.gss_enc AS encrypted
+pg_stat_gssapi| SELECT pid,
+    gss_auth AS gss_authenticated,
+    gss_princ AS principal,
+    gss_enc AS encrypted
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1895,8 +1895,8 @@ pg_stat_progress_analyze| SELECT s.pid,
     (s.param8)::oid AS current_child_table_relid
    FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_progress_basebackup| SELECT s.pid,
-        CASE s.param1
+pg_stat_progress_basebackup| SELECT pid,
+        CASE param1
             WHEN 0 THEN 'initializing'::text
             WHEN 1 THEN 'waiting for checkpoint to finish'::text
             WHEN 2 THEN 'estimating backup size'::text
@@ -1905,13 +1905,13 @@ pg_stat_progress_basebackup| SELECT s.pid,
             WHEN 5 THEN 'transferring wal files'::text
             ELSE NULL::text
         END AS phase,
-        CASE s.param2
+        CASE param2
             WHEN '-1'::integer THEN NULL::bigint
-            ELSE s.param2
+            ELSE param2
         END AS backup_total,
-    s.param3 AS backup_streamed,
-    s.param4 AS tablespaces_total,
-    s.param5 AS tablespaces_streamed
+    param3 AS backup_streamed,
+    param4 AS tablespaces_total,
+    param5 AS tablespaces_streamed
    FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
@@ -2021,16 +2021,16 @@ pg_stat_progress_vacuum| SELECT s.pid,
     s.param7 AS num_dead_tuples
    FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
-pg_stat_recovery_prefetch| SELECT s.stats_reset,
-    s.prefetch,
-    s.hit,
-    s.skip_init,
-    s.skip_new,
-    s.skip_fpw,
-    s.skip_rep,
-    s.wal_distance,
-    s.block_distance,
-    s.io_depth
+pg_stat_recovery_prefetch| SELECT stats_reset,
+    prefetch,
+    hit,
+    skip_init,
+    skip_new,
+    skip_fpw,
+    skip_rep,
+    wal_distance,
+    block_distance,
+    io_depth
    FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
@@ -2068,26 +2068,26 @@ pg_stat_replication_slots| SELECT s.slot_name,
    FROM pg_replication_slots r,
     LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
   WHERE (r.datoid IS NOT NULL);
-pg_stat_slru| SELECT s.name,
-    s.blks_zeroed,
-    s.blks_hit,
-    s.blks_read,
-    s.blks_written,
-    s.blks_exists,
-    s.flushes,
-    s.truncates,
-    s.stats_reset
+pg_stat_slru| SELECT name,
+    blks_zeroed,
+    blks_hit,
+    blks_read,
+    blks_written,
+    blks_exists,
+    flushes,
+    truncates,
+    stats_reset
    FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset);
-pg_stat_ssl| SELECT s.pid,
-    s.ssl,
-    s.sslversion AS version,
-    s.sslcipher AS cipher,
-    s.sslbits AS bits,
-    s.ssl_client_dn AS client_dn,
-    s.ssl_client_serial AS client_serial,
-    s.ssl_issuer_dn AS issuer_dn
+pg_stat_ssl| SELECT pid,
+    ssl,
+    sslversion AS version,
+    sslcipher AS cipher,
+    sslbits AS bits,
+    ssl_client_dn AS client_dn,
+    ssl_client_serial AS client_serial,
+    ssl_issuer_dn AS issuer_dn
    FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id)
-  WHERE (s.client_port IS NOT NULL);
+  WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
@@ -2106,44 +2106,44 @@ pg_stat_subscription_stats| SELECT ss.subid,
     ss.stats_reset
    FROM pg_subscription s,
     LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset);
-pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.last_idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.last_seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.last_idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_stat_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    last_seq_scan,
+    seq_tup_read,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2153,71 +2153,71 @@ pg_stat_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL));
-pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid,
-    pg_stat_all_indexes.indexrelid,
-    pg_stat_all_indexes.schemaname,
-    pg_stat_all_indexes.relname,
-    pg_stat_all_indexes.indexrelname,
-    pg_stat_all_indexes.idx_scan,
-    pg_stat_all_indexes.last_idx_scan,
-    pg_stat_all_indexes.idx_tup_read,
-    pg_stat_all_indexes.idx_tup_fetch
+pg_stat_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_read,
+    idx_tup_fetch
    FROM pg_stat_all_indexes
-  WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
-    pg_stat_all_tables.schemaname,
-    pg_stat_all_tables.relname,
-    pg_stat_all_tables.seq_scan,
-    pg_stat_all_tables.last_seq_scan,
-    pg_stat_all_tables.seq_tup_read,
-    pg_stat_all_tables.idx_scan,
-    pg_stat_all_tables.last_idx_scan,
-    pg_stat_all_tables.idx_tup_fetch,
-    pg_stat_all_tables.n_tup_ins,
-    pg_stat_all_tables.n_tup_upd,
-    pg_stat_all_tables.n_tup_del,
-    pg_stat_all_tables.n_tup_hot_upd,
-    pg_stat_all_tables.n_live_tup,
-    pg_stat_all_tables.n_dead_tup,
-    pg_stat_all_tables.n_mod_since_analyze,
-    pg_stat_all_tables.n_ins_since_vacuum,
-    pg_stat_all_tables.last_vacuum,
-    pg_stat_all_tables.last_autovacuum,
-    pg_stat_all_tables.last_analyze,
-    pg_stat_all_tables.last_autoanalyze,
-    pg_stat_all_tables.vacuum_count,
-    pg_stat_all_tables.autovacuum_count,
-    pg_stat_all_tables.analyze_count,
-    pg_stat_all_tables.autoanalyze_count
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    last_seq_scan,
+    seq_tup_read,
+    idx_scan,
+    last_idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd,
+    n_live_tup,
+    n_dead_tup,
+    n_mod_since_analyze,
+    n_ins_since_vacuum,
+    last_vacuum,
+    last_autovacuum,
+    last_analyze,
+    last_autoanalyze,
+    vacuum_count,
+    autovacuum_count,
+    analyze_count,
+    autoanalyze_count
    FROM pg_stat_all_tables
-  WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
-pg_stat_wal| SELECT w.wal_records,
-    w.wal_fpi,
-    w.wal_bytes,
-    w.wal_buffers_full,
-    w.wal_write,
-    w.wal_sync,
-    w.wal_write_time,
-    w.wal_sync_time,
-    w.stats_reset
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_stat_wal| SELECT wal_records,
+    wal_fpi,
+    wal_bytes,
+    wal_buffers_full,
+    wal_write,
+    wal_sync,
+    wal_write_time,
+    wal_sync_time,
+    stats_reset
    FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset);
-pg_stat_wal_receiver| SELECT s.pid,
-    s.status,
-    s.receive_start_lsn,
-    s.receive_start_tli,
-    s.written_lsn,
-    s.flushed_lsn,
-    s.received_tli,
-    s.last_msg_send_time,
-    s.last_msg_receipt_time,
-    s.latest_end_lsn,
-    s.latest_end_time,
-    s.slot_name,
-    s.sender_host,
-    s.sender_port,
-    s.conninfo
+pg_stat_wal_receiver| SELECT pid,
+    status,
+    receive_start_lsn,
+    receive_start_tli,
+    written_lsn,
+    flushed_lsn,
+    received_tli,
+    last_msg_send_time,
+    last_msg_receipt_time,
+    latest_end_lsn,
+    latest_end_time,
+    slot_name,
+    sender_host,
+    sender_port,
+    conninfo
    FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo)
-  WHERE (s.pid IS NOT NULL);
+  WHERE (pid IS NOT NULL);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
@@ -2234,19 +2234,19 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
-pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2256,19 +2256,19 @@ pg_stat_xact_user_functions| SELECT p.oid AS funcid,
    FROM (pg_proc p
      LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace)))
   WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL));
-pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
-    pg_stat_xact_all_tables.schemaname,
-    pg_stat_xact_all_tables.relname,
-    pg_stat_xact_all_tables.seq_scan,
-    pg_stat_xact_all_tables.seq_tup_read,
-    pg_stat_xact_all_tables.idx_scan,
-    pg_stat_xact_all_tables.idx_tup_fetch,
-    pg_stat_xact_all_tables.n_tup_ins,
-    pg_stat_xact_all_tables.n_tup_upd,
-    pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+pg_stat_xact_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    seq_scan,
+    seq_tup_read,
+    idx_scan,
+    idx_tup_fetch,
+    n_tup_ins,
+    n_tup_upd,
+    n_tup_del,
+    n_tup_hot_upd
    FROM pg_stat_xact_all_tables
-  WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
     i.oid AS indexrelid,
     n.nspname AS schemaname,
@@ -2312,64 +2312,64 @@ pg_statio_all_tables| SELECT c.oid AS relid,
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
-pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+pg_statio_sys_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text));
-pg_statio_sys_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_sys_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text));
-pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid,
-    pg_statio_all_indexes.indexrelid,
-    pg_statio_all_indexes.schemaname,
-    pg_statio_all_indexes.relname,
-    pg_statio_all_indexes.indexrelname,
-    pg_statio_all_indexes.idx_blks_read,
-    pg_statio_all_indexes.idx_blks_hit
+  WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
+pg_statio_user_indexes| SELECT relid,
+    indexrelid,
+    schemaname,
+    relname,
+    indexrelname,
+    idx_blks_read,
+    idx_blks_hit
    FROM pg_statio_all_indexes
-  WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
-pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid,
-    pg_statio_all_sequences.schemaname,
-    pg_statio_all_sequences.relname,
-    pg_statio_all_sequences.blks_read,
-    pg_statio_all_sequences.blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_sequences| SELECT relid,
+    schemaname,
+    relname,
+    blks_read,
+    blks_hit
    FROM pg_statio_all_sequences
-  WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
-pg_statio_user_tables| SELECT pg_statio_all_tables.relid,
-    pg_statio_all_tables.schemaname,
-    pg_statio_all_tables.relname,
-    pg_statio_all_tables.heap_blks_read,
-    pg_statio_all_tables.heap_blks_hit,
-    pg_statio_all_tables.idx_blks_read,
-    pg_statio_all_tables.idx_blks_hit,
-    pg_statio_all_tables.toast_blks_read,
-    pg_statio_all_tables.toast_blks_hit,
-    pg_statio_all_tables.tidx_blks_read,
-    pg_statio_all_tables.tidx_blks_hit
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
+pg_statio_user_tables| SELECT relid,
+    schemaname,
+    relname,
+    heap_blks_read,
+    heap_blks_hit,
+    idx_blks_read,
+    idx_blks_hit,
+    toast_blks_read,
+    toast_blks_hit,
+    tidx_blks_read,
+    tidx_blks_hit
    FROM pg_statio_all_tables
-  WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
+  WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_stats| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     a.attname,
@@ -2554,24 +2554,24 @@ pg_tables| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev,
-    pg_timezone_abbrevs.utc_offset,
-    pg_timezone_abbrevs.is_dst
+pg_timezone_abbrevs| SELECT abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
-pg_timezone_names| SELECT pg_timezone_names.name,
-    pg_timezone_names.abbrev,
-    pg_timezone_names.utc_offset,
-    pg_timezone_names.is_dst
+pg_timezone_names| SELECT name,
+    abbrev,
+    utc_offset,
+    is_dst
    FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
-pg_user| SELECT pg_shadow.usename,
-    pg_shadow.usesysid,
-    pg_shadow.usecreatedb,
-    pg_shadow.usesuper,
-    pg_shadow.userepl,
-    pg_shadow.usebypassrls,
+pg_user| SELECT usename,
+    usesysid,
+    usecreatedb,
+    usesuper,
+    userepl,
+    usebypassrls,
     '********'::text AS passwd,
-    pg_shadow.valuntil,
-    pg_shadow.useconfig
+    valuntil,
+    useconfig
    FROM pg_shadow;
 pg_user_mappings| SELECT u.oid AS umid,
     s.oid AS srvid,
@@ -3091,7 +3091,7 @@ SELECT * FROM rule_v1;
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rule_t1.a
+ SELECT a
    FROM rule_t1;
 Rules:
  newinsertrule AS
@@ -3130,8 +3130,8 @@ alter table rule_v1 rename column column2 to q2;
  column1 | integer |           |          |         | plain   | 
  q2      | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1,
-    "*VALUES*".column2 AS q2
+ SELECT column1,
+    column2 AS q2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3143,8 +3143,8 @@ create view rule_v1(x) as values(1,2);
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT "*VALUES*".column1 AS x,
-    "*VALUES*".column2
+ SELECT column1 AS x,
+    column2
    FROM (VALUES (1,2)) "*VALUES*";
 
 drop view rule_v1;
@@ -3156,8 +3156,8 @@ create view rule_v1(x) as select * from (values(1,2)) v;
  x       | integer |           |          |         | plain   | 
  column2 | integer |           |          |         | plain   | 
 View definition:
- SELECT v.column1 AS x,
-    v.column2
+ SELECT column1 AS x,
+    column2
    FROM ( VALUES (1,2)) v;
 
 drop view rule_v1;
@@ -3169,8 +3169,8 @@ create view rule_v1(x) as select * from (values(1,2)) v(q,w);
  x      | integer |           |          |         | plain   | 
  w      | integer |           |          |         | plain   | 
 View definition:
- SELECT v.q AS x,
-    v.w
+ SELECT q AS x,
+    w
    FROM ( VALUES (1,2)) v(q, w);
 
 drop view rule_v1;
diff --git a/src/test/regress/expected/tablesample.out b/src/test/regress/expected/tablesample.out
index 60bb4e8e3e..9ff4611640 100644
--- a/src/test/regress/expected/tablesample.out
+++ b/src/test/regress/expected/tablesample.out
@@ -74,7 +74,7 @@ CREATE VIEW test_tablesample_v2 AS
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2);
 
 \d+ test_tablesample_v2
@@ -83,7 +83,7 @@ View definition:
 --------+---------+-----------+----------+---------+---------+-------------
  id     | integer |           |          |         | plain   | 
 View definition:
- SELECT test_tablesample.id
+ SELECT id
    FROM test_tablesample TABLESAMPLE system (99);
 
 -- check a sampled query doesn't affect cursor in progress
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 6d80ab1a6d..7dbeced570 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1277,8 +1277,8 @@ DROP TRIGGER instead_of_delete_trig ON main_view;
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT main_table.a,
-    main_table.b
+ SELECT a,
+    b
    FROM main_table;
 Triggers:
     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt')
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 5a47dacad9..2b578cced1 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1925,19 +1925,19 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
  a      | integer |           |          |         | plain   | 
  b      | integer |           |          |         | plain   | 
 View definition:
- SELECT base_tbl.a,
-    base_tbl.b
+ SELECT a,
+    b
    FROM base_tbl
-  WHERE base_tbl.a < base_tbl.b;
+  WHERE a < b;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
- table_catalog | table_schema | table_name |          view_definition           | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a,               +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |     base_tbl.b                    +|              |              |                    |                      |                      | 
-               |              |            |    FROM base_tbl                  +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (base_tbl.a < base_tbl.b); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name | view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a,      +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |     b           +|              |              |                    |                      |                      | 
+               |              |            |    FROM base_tbl+|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < b); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
@@ -1978,17 +1978,17 @@ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=cascaded
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
@@ -2018,17 +2018,17 @@ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 Options: check_option=local
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| LOCAL        | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
@@ -2059,16 +2059,16 @@ ALTER VIEW rw_view2 RESET (check_option);
 --------+---------+-----------+----------+---------+---------+-------------
  a      | integer |           |          |         | plain   | 
 View definition:
- SELECT rw_view1.a
+ SELECT a
    FROM rw_view1
-  WHERE rw_view1.a < 10;
+  WHERE a < 10;
 
 SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
- table_catalog | table_schema | table_name |      view_definition       | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view2   |  SELECT rw_view1.a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1          +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a < 10); |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a < 10); |              |              |                    |                      |                      | 
 (1 row)
 
 INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
@@ -2090,15 +2090,15 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
 CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
 SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
- table_catalog | table_schema | table_name |      view_definition      | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
----------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
- regression    | public       | rw_view1   |  SELECT base_tbl.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM base_tbl;         |              |              |                    |                      |                      | 
- regression    | public       | rw_view2   |  SELECT rw_view1.a       +| NONE         | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view1         +|              |              |                    |                      |                      | 
-               |              |            |   WHERE (rw_view1.a > 0); |              |              |                    |                      |                      | 
- regression    | public       | rw_view3   |  SELECT rw_view2.a       +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
-               |              |            |    FROM rw_view2;         |              |              |                    |                      |                      | 
+ table_catalog | table_schema | table_name |  view_definition  | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into 
+---------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression    | public       | rw_view1   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM base_tbl; |              |              |                    |                      |                      | 
+ regression    | public       | rw_view2   |  SELECT a        +| NONE         | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view1 +|              |              |                    |                      |                      | 
+               |              |            |   WHERE (a > 0);  |              |              |                    |                      |                      | 
+ regression    | public       | rw_view3   |  SELECT a        +| CASCADED     | YES          | YES                | NO                   | NO                   | NO
+               |              |            |    FROM rw_view2; |              |              |                    |                      |                      | 
 (3 rows)
 
 INSERT INTO rw_view1 VALUES (-1); -- ok
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 170bea23c2..3d1d26aa39 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1212,10 +1212,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1238,10 +1238,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                              pg_get_viewdef                                               
------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                             +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
+                                            pg_get_viewdef                                             
+-------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                           +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1264,10 +1264,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                            
------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                       +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+                                         pg_get_viewdef                                          
+-------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                     +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1290,10 +1290,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                           pg_get_viewdef                                           
-----------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                      +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
+                                         pg_get_viewdef                                         
+------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                    +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1316,10 +1316,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                    pg_get_viewdef                                     
----------------------------------------------------------------------------------------
-  SELECT i.i,                                                                         +
-     sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                  pg_get_viewdef                                   
+-----------------------------------------------------------------------------------
+  SELECT i,                                                                       +
+     sum(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1341,10 +1341,10 @@ SELECT * FROM v_window;
 (10 rows)
 
 SELECT pg_get_viewdef('v_window');
-                                     pg_get_viewdef                                      
------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                           +
-     sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
+                                   pg_get_viewdef                                    
+-------------------------------------------------------------------------------------
+  SELECT i,                                                                         +
+     sum(i) OVER (ORDER BY i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+
     FROM generate_series(1, 10) i(i);
 (1 row)
 
@@ -1353,10 +1353,10 @@ CREATE TEMP VIEW v_window AS
 	SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i
   FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i;
 SELECT pg_get_viewdef('v_window');
-                                                      pg_get_viewdef                                                       
----------------------------------------------------------------------------------------------------------------------------
-  SELECT i.i,                                                                                                             +
-     min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
+                                                    pg_get_viewdef                                                     
+-----------------------------------------------------------------------------------------------------------------------
+  SELECT i,                                                                                                           +
+     min(i) OVER (ORDER BY i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index f3fd1cd32a..008a8a9781 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -396,9 +396,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass);
              subdepartment sd                 +
            WHERE (d.parent_department = sd.id)+
          )                                    +
-  SELECT subdepartment.id,                    +
-     subdepartment.parent_department,         +
-     subdepartment.name                       +
+  SELECT id,                                  +
+     parent_department,                       +
+     name                                     +
     FROM subdepartment;
 (1 row)
 
@@ -419,9 +419,9 @@ SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
              subdepartment sd               +
            WHERE d.parent_department = sd.id+
          )                                  +
-  SELECT subdepartment.id,                  +
-     subdepartment.parent_department,       +
-     subdepartment.name                     +
+  SELECT id,                                +
+     parent_department,                     +
+     name                                   +
     FROM subdepartment;
 (1 row)
 
@@ -446,7 +446,7 @@ View definition:
            FROM t t_1
           WHERE t_1.n < 100
         )
- SELECT sum(t.n) AS sum
+ SELECT sum(n) AS sum
    FROM t;
 
 -- corner case in which sub-WITH gets initialized first
@@ -959,9 +959,9 @@ select pg_get_viewdef('v_search');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) SEARCH DEPTH FIRST BY f, t SET seq  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1524,9 +1524,9 @@ select pg_get_viewdef('v_cycle1');
              search_graph sg                   +
            WHERE (g.f = sg.t)                  +
          ) CYCLE f, t SET is_cycle USING path  +
-  SELECT search_graph.f,                       +
-     search_graph.t,                           +
-     search_graph.label                        +
+  SELECT f,                                    +
+     t,                                        +
+     label                                     +
     FROM search_graph;
 (1 row)
 
@@ -1546,9 +1546,9 @@ select pg_get_viewdef('v_cycle2');
              search_graph sg                                                +
            WHERE (g.f = sg.t)                                               +
          ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
-  SELECT search_graph.f,                                                    +
-     search_graph.t,                                                        +
-     search_graph.label                                                     +
+  SELECT f,                                                                 +
+     t,                                                                     +
+     label                                                                  +
     FROM search_graph;
 (1 row)
 
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 948b4e702c..cc213523c0 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -603,12 +603,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 5fd3886b5e..3986fc1706 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -583,12 +583,12 @@ CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10));
 CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text);
 SELECT table_name, view_definition FROM information_schema.views
   WHERE table_name LIKE 'xmlview%' ORDER BY 1;
- table_name |                                                  view_definition                                                  
-------------+-------------------------------------------------------------------------------------------------------------------
+ table_name |                                              view_definition                                               
+------------+------------------------------------------------------------------------------------------------------------
  xmlview1   |  SELECT xmlcomment('test'::text) AS xmlcomment;
  xmlview2   |  SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat";
  xmlview3   |  SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement";
- xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(emp.name AS name, emp.age AS age, emp.salary AS pay)) AS "xmlelement"+
+ xmlview4   |  SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement"     +
             |    FROM emp;
  xmlview5   |  SELECT XMLPARSE(CONTENT '<abc>x</abc>'::text STRIP WHITESPACE) AS "xmlparse";
  xmlview6   |  SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi";
-- 
2.35.3



  [application/octet-stream] v24-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (26.1K, ../../CA+HiwqEYCLRZ2Boq_uK0pjLn_9b8XL-LmwKj7HN5kJOivUkYLg@mail.gmail.com/4-v24-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From e81ef07fbc6afa742d3f8bb54d770d73268bbeee Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v24 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++---
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 38 +++++++++++++++++++
 src/backend/optimizer/prep/preptlist.c   | 48 ++++++++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 25 ++++++------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 ----------------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 131 insertions(+), 76 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 461230b011..ede4c98875 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1378,20 +1378,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d8fd3cfdbe..6f55a859ae 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3971,6 +3971,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3beb907ea..139d8e095f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -569,7 +569,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e5c993c90d..aa7077c7f8 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -536,7 +536,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5013ac3377..2360ae280b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5cddc9d6cf..4178eb416d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,35 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+						{
+							extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+															  extraUpdatedCols);
+						}
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1890,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						/* See the comment in the inherited UPDATE block. */
+						if (root->extraUpdatedCols)
+						{
+							root->extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+						}
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1912,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE/MERGE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					/* See the comment in the inherited UPDATE block. */
+					if (root->extraUpdatedCols)
+					{
+						root->extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1959,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e5c1103316..6bbcb6ac1d 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,42 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 03e04094e0..b4772870eb 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -148,6 +149,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   root_perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -313,6 +315,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -343,7 +346,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -412,14 +415,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -456,7 +463,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 								Index *childRTindex_p)
 {
 	Query	   *parse = root->parse;
-	Oid			parentOID = RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
 	Index		childRTindex;
@@ -486,7 +492,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/* A partitioned child will need to be expanded further. */
 	if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		Assert(childOID != parentOID);
+		Assert(childOID != RelationGetRelid(parentrel));
 		childrte->inh = true;
 	}
 	else
@@ -553,13 +559,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -866,7 +865,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
+Bitmapset *
 translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
 							   RelOptInfo *top_parent_rel,
 							   Bitmapset *top_parent_cols)
@@ -941,12 +940,12 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
 													 perminfo->updatedCols);
 		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
-														  rte->extraUpdatedCols);
+														  root->extraUpdatedCols);
 	}
 	else
 	{
 		updatedCols = perminfo->updatedCols;
-		extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
 	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 6dd11329fb..5b489da57d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3644,6 +3644,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3659,6 +3661,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3723,6 +3726,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 568344cc23..eb18a04908 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1815,7 +1816,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1864,7 +1864,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1882,7 +1881,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 77cd294e51..06cacc3ecc 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1626,46 +1626,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3704,7 +3664,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3716,7 +3675,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3801,9 +3759,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2d648a8fdd..820eb2105c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1153,7 +1153,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 7423df3614..7adb66cac8 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2250,6 +2252,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e3a5233dd7..2e7b9a289d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



  [application/octet-stream] v24-0001-Rework-query-relation-permission-checking.patch (145.9K, ../../CA+HiwqEYCLRZ2Boq_uK0pjLn_9b8XL-LmwKj7HN5kJOivUkYLg@mail.gmail.com/5-v24-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From f8cc6b57cef7a9261a6910c50c31f71386ec240f Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v24 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  82 +++++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |   6 +-
 src/backend/executor/execMain.c               | 115 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 133 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  43 +++-
 src/backend/optimizer/plan/subselect.c        |   7 +
 src/backend/optimizer/prep/prepjointree.c     |  20 +-
 src/backend/optimizer/util/inherit.c          | 167 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  65 +++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 175 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 183 ++++++++----------
 src/backend/rewrite/rewriteManip.c            |  25 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   2 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 963 insertions(+), 563 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d7500abfb..a3ec0ecdb4 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -39,6 +40,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "postgres_fdw.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
@@ -459,7 +461,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +627,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +660,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1512,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1812,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1890,6 +1892,36 @@ postgresPlanForeignModify(PlannerInfo *root,
 					  retrieved_attrs);
 }
 
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in foreign table result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+static Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
 /*
  * postgresBeginForeignModify
  *		Begin an insert/update/delete operation on a foreign table
@@ -1901,6 +1933,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1941,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1965,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1977,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2154,6 +2190,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2231,6 +2268,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2247,7 +2286,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2633,7 +2673,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2653,13 +2692,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3962,12 +4000,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3979,12 +4017,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..c4e071b0ea 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst(lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..6e7f96e7db 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..5ae68842df 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a079c70152..03f8ec459a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 659e189549..feb031e997 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1288,7 +1288,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6007e10730..661f091921 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10022,7 +10025,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10219,7 +10223,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12335,7 +12340,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18043,7 +18049,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18981,7 +18988,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6f07ac2a9c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -411,10 +411,6 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
-
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	viewParse->rtable = new_rt;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..c43d2215b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -74,8 +74,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +90,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +554,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +565,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +655,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +687,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +703,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +763,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +802,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1846,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1931,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1984,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2092,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..461230b011 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,64 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1318,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 64c65f060b..b91e235423 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -504,6 +504,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -557,11 +558,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b4ff855f7c..75bf11c741 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -468,6 +468,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -531,11 +532,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ac86ce9003..5013ac3377 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 493a3af0fa..5cddc9d6cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6265,6 +6269,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6392,6 +6397,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..a26aa36048 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -355,6 +355,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -370,14 +373,29 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 	 * flattened rangetable match up with their original indexes.  When
 	 * recursing, we only care about extracting relation RTEs.
 	 */
+	rti = 1;
 	foreach(lc, root->parse->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * Update perminfoindex, if any, to reflect the correponding
+			 * RTEPermissionInfo's position in the flattened list.
+			 */
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += list_length(glob->finalrtepermlist);
+
 			add_rte_to_flat_rtable(glob, rte);
+		}
+
+		rti++;
 	}
 
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 root->parse->rtepermlist);
+
 	/*
 	 * If there are any dead subqueries, they are not referenced in the Plan
 	 * tree, so we must add RTEs contained in them to the flattened rtable
@@ -450,6 +468,15 @@ flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 							 flatten_rtes_walker,
 							 (void *) glob,
 							 QTW_EXAMINE_RTES_BEFORE);
+
+	/*
+	 * Now add the subquery's RTEPermissionInfos too.  flatten_rtes_walker()
+	 * should already have updated the perminfoindex in the RTEs in the
+	 * subquery to reflect the corresponding RTEPermissionInfos' position in
+	 * finalrtepermlist.
+	 */
+	glob->finalrtepermlist = list_concat(glob->finalrtepermlist,
+										 rte->subquery->rtepermlist);
 }
 
 static bool
@@ -463,7 +490,15 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
+		{
+			/*
+			 * The correponding RTEPermissionInfo will get added to
+			 * finalrtepermlist, so adjust perminfoindex accordingly.
+			 */
+			Assert(rte->perminfoindex > 0);
+			rte->perminfoindex += list_length(glob->finalrtepermlist);
 			add_rte_to_flat_rtable(glob, rte);
+		}
 		return false;
 	}
 	if (IsA(node, Query))
@@ -483,10 +518,10 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..a61082d27c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subselect->rtepermlist,
+								 subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index b4b9099eb6..b2ab2c14a4 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -175,13 +175,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1204,6 +1197,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&parse->rtepermlist, subquery->rtepermlist,
+								 subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1344,6 +1344,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	ConcatRTEPermissionInfoLists(&root->parse->rtepermlist,
+								 subquery->rtepermlist, rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..03e04094e0 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *root_perminfo =
+			GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +146,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   root_perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +312,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +332,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +409,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +468,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,7 +491,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
 	/* Link not-yet-fully-filled child RTE into data structures */
@@ -539,33 +553,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +859,95 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index	use_relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
+	Assert(rte->perminfoindex > 0);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  rte->extraUpdatedCols);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = rte->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 1786a3dadd..7e1db947b6 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -236,7 +237,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo =
+				GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..25324d9486 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo =
+									GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 7913523b1c..79171fb725 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -214,6 +214,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -286,7 +287,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -345,7 +346,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -359,8 +360,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..4dc8d7ecf5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,10 +1021,13 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo =
+			GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
 										   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
@@ -1235,10 +1238,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1280,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1424,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1461,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1470,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1485,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1522,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1546,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1555,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1570,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1644,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1969,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1987,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2054,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2135,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2226,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2247,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2371,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2491,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2509,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3134,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3152,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3707,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index.  */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->perminfoindex > 0);
+	if (rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %u in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = (RTEPermissionInfo *) list_nth(rtepermlist,
+											  rte->perminfoindex - 1);
+	Assert(perminfo != NULL);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..0415fcec7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 8140e79d8f..03c4717cbd 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e48a3f589a..568344cc23 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 09165b269b..1cb8e1b441 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -797,14 +797,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -831,18 +831,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = (RTEPermissionInfo *) lfirst(l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..fda0eacf79 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -395,32 +396,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	sub_action->rtepermlist = copyObject(sub_action->rtepermlist);
+	query_rtable = copyObject(parsetree->rtable);
+	ConcatRTEPermissionInfoLists(&sub_action->rtepermlist,
+								 parsetree->rtepermlist, query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1628,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1657,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1741,8 +1748,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  List *activeRIRs)
 {
 	Query	   *rule_action;
-	RangeTblEntry *rte,
-			   *subrte;
+	RangeTblEntry *rte;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1789,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1852,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1881,28 +1869,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;
 	rte->inh = false;			/* must not be set for a subquery */
 
-	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
-	 */
-	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
-	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	return parsetree;
 }
 
@@ -1931,8 +1900,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3046,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3185,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3257,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3413,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3424,6 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3697,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3709,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3796,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3552a8db59 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,28 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+void
+ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(*dest_rtepermlist);
+
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
+
+	foreach(l, src_rtable)
+	{
+		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += offset;
+	}
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 61c2eecaca..dc837af316 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d597b7e81f..b7f1d87715 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5142,7 +5142,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5194,7 +5194,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5277,7 +5277,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5325,7 +5325,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5386,6 +5386,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5394,7 +5395,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5463,7 +5464,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..787ad8b232 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..3d2de0ae1b 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..89b10ee909 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -600,6 +601,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7caff62af7..2d648a8fdd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -966,37 +969,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1052,11 +1024,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the query's list of RTEPermissionInfos; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1176,14 +1153,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns the need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 09342d128d..7423df3614 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 5c2ab1b379..e3a5233dd7 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -75,6 +75,10 @@ typedef struct PlannedStmt
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	List	   *appendRelations;	/* list of AppendRelInfo nodes */
 
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0379dd9673 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,7 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern void ConcatRTEPermissionInfoLists(List **dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-10 11:58  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Alvaro Herrera @ 2022-11-10 11:58 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hello

I've been trying to understand what 0001 does.  One thing that strikes
me is that it seems like it'd be easy to have bugs because of modifying
the perminfo list inadequately.  I couldn't find any cross-check that
some perminfo element that we obtain for a rte does actually match the
relation we wanted to check.  Maybe we could add a test in some central
place that perminfo->relid equals rte->relid?

A related point is that concatenating lists doesn't seem to worry about
not processing one element multiple times and ending up with bogus offsets.
(I suppose you still have to let an element be processed multiple times
in case you have nested subqueries?  I wonder how good is the test
coverage for such scenarios.)

Why do callers of add_rte_to_flat_rtable() have to modify the rte's
perminfoindex themselves, instead of having the function do it for them?
That looks strange.  But also it's odd that flatten_unplanned_rtes
concatenates the two lists after all the perminfoindexes have been
modified, rather than doing both things (adding each RTEs perminfo to
the global list and offsetting the index) as we walk the list, in
flatten_rtes_walker.  It looks like these two actions are disconnected
from one another, but unless I misunderstand, in reality the opposite is
true.

I think the API of ConcatRTEPermissionInfoLists is a bit weird.  Why not
have the function return the resulting list instead, just like
list_append?  It is more verbose, but it seems easier to grok.

Two trivial changes attached.  (Maybe 0002 is not correct, if you're
also trying to reference finalrtepermlist; but in that case I think the
original may have been misleading as well.)

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-10 12:19  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  2 siblings, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2022-11-10 12:19 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-Oct-06, Amit Langote wrote:

> Actually, List of Bitmapsets turned out to be something that doesn't
> just-work with our Node infrastructure, which I found out thanks to
> -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> first-class support for copy/equal/write/read support for Bitmapsets,
> such that writeNode() can write appropriately labeled versions of them
> and nodeRead() can read them as Bitmapsets.  That's done in 0003.  I
> didn't actually go ahead and make *all* Bitmapsets in the plan trees
> to be Nodes, but maybe 0003 can be expanded to do that.  We won't need
> to make gen_node_support.pl emit *_BITMAPSET_FIELD() blurbs then; can
> just use *_NODE_FIELD().

Hmm, is this related to what Tom posted as part of his 0004 in
https://postgr.es/m/[email protected]

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-11 16:46  Tom Lane <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Tom Lane @ 2022-11-11 16:46 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Amit Langote <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Alvaro Herrera <[email protected]> writes:
> On 2022-Oct-06, Amit Langote wrote:
>> Actually, List of Bitmapsets turned out to be something that doesn't
>> just-work with our Node infrastructure, which I found out thanks to
>> -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
>> first-class support for copy/equal/write/read support for Bitmapsets,

> Hmm, is this related to what Tom posted as part of his 0004 in
> https://postgr.es/m/[email protected]

It could be.  For some reason I thought that Amit had withdrawn
his proposal to make Bitmapsets be Nodes.  But if it's still live,
then the data structure I invented in my 0004 could plausibly be
replaced by a List of Bitmapsets.

The code I was using that for would rather have fixed-size arrays
of Bitmapsets than variable-size Lists, mainly because it always
knows ab initio what the max length of the array will be.  But
I don't think that the preference is so strong that it justifies
a private data structure.

The main thing I was wondering about in connection with that
was whether to assume that there could be other future applications
of the logic to perform multi-bitmapset union, intersection,
etc.  If so, then I'd be inclined to choose different naming and
put those functions in or near to bitmapset.c.  It doesn't look
like Amit's code needs anything like that, but maybe somebody
has an idea about other applications?

Anyway, I concur with Peter's upthread comment that making
Bitmapsets be Nodes is probably justifiable all by itself.
The lack of a Node tag in them now is just because in a 32-bit
world it seemed like unnecessary bloat.  But on 64-bit machines
it's free, and we aren't optimizing for 32-bit any more.

I do not like the details of v24-0003 at all though, because
it seems to envision that a "node Bitmapset" is a different
thing from a raw Bitmapset.  That can only lead to bugs ---
why would we not make it the case that every Bitmapset is
properly labeled with the node tag?

Also, although I'm on board with making Bitmapsets be Nodes,
I don't think I'm on board with changing their dump format.
Planner node dumps would get enormously bulkier and less
readable if we changed things like

   :relids (b 1 2)

to

   :relids
      {BITMAPSET
       (b 1 2)
      }

or whatever the output would look like as the patch stands.
So that needs a bit more effort, but it's surely manageable.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-14 07:32  Amit Langote <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-11-14 07:32 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sat, Nov 12, 2022 at 1:46 AM Tom Lane <[email protected]> wrote:
> Alvaro Herrera <[email protected]> writes:
> > On 2022-Oct-06, Amit Langote wrote:
> >> Actually, List of Bitmapsets turned out to be something that doesn't
> >> just-work with our Node infrastructure, which I found out thanks to
> >> -DWRITE_READ_PARSE_PLAN_TREES.  So, I had to go ahead and add
> >> first-class support for copy/equal/write/read support for Bitmapsets,
>
> > Hmm, is this related to what Tom posted as part of his 0004 in
> > https://postgr.es/m/[email protected]
>
> It could be.  For some reason I thought that Amit had withdrawn
> his proposal to make Bitmapsets be Nodes.

I think you are referring to [1] that I had forgotten to link to here.
I did reimplement a data structure in my patch on the "Re: generic
plans and initial pruning" thread to stop using a List of Bitmapsets,
so the Bitmapset as Nodes functionality became unnecessary there,
though I still need it for the proposal here to move
extraUpdatedColumns (patch 0004) into ModifyTable node.

> The code I was using that for would rather have fixed-size arrays
> of Bitmapsets than variable-size Lists, mainly because it always
> knows ab initio what the max length of the array will be.  But
> I don't think that the preference is so strong that it justifies
> a private data structure.
>
> The main thing I was wondering about in connection with that
> was whether to assume that there could be other future applications
> of the logic to perform multi-bitmapset union, intersection,
> etc.  If so, then I'd be inclined to choose different naming and
> put those functions in or near to bitmapset.c.  It doesn't look
> like Amit's code needs anything like that, but maybe somebody
> has an idea about other applications?

Yes, simple storage of multiple Bitmapsets in a List somewhere in a
parse/plan tree sounded like that would have wider enough use to add
proper node support for.   Assuming you mean trying to generalize
VarAttnoSet in your patch 0004 posted at [2], I wonder if you want to
somehow make its indexability by varno / RT index a part of the
interface of the generic code you're thinking for it?  For example,
varattnoset_*_members collection of routines in that patch seem to
assume that the Bitmapsets at a given index in the provided pair of
VarAttnoSets are somehow related -- covering to the same base relation
in this case.  That does not sound very generalizable but maybe that
is not what you are thinking at all.

> Anyway, I concur with Peter's upthread comment that making
> Bitmapsets be Nodes is probably justifiable all by itself.
> The lack of a Node tag in them now is just because in a 32-bit
> world it seemed like unnecessary bloat.  But on 64-bit machines
> it's free, and we aren't optimizing for 32-bit any more.
>
> I do not like the details of v24-0003 at all though, because
> it seems to envision that a "node Bitmapset" is a different
> thing from a raw Bitmapset.  That can only lead to bugs ---
> why would we not make it the case that every Bitmapset is
> properly labeled with the node tag?

Yeah, I just didn't think hard enough to realize that having
bitmapset.c itself set the node tag is essentially free, and it looks
like a better design anyway than the design where callers get to
choose to make the bitmapset they are manipulating a Node or not.

> Also, although I'm on board with making Bitmapsets be Nodes,
> I don't think I'm on board with changing their dump format.
> Planner node dumps would get enormously bulkier and less
> readable if we changed things like
>
>    :relids (b 1 2)
>
> to
>
>    :relids
>       {BITMAPSET
>        (b 1 2)
>       }
>
> or whatever the output would look like as the patch stands.
> So that needs a bit more effort, but it's surely manageable.

Agreed with leaving the dump format unchanged or not bloating it.

Thanks a lot for 5e1f3b9ebf6e5.

--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

[1] https://www.postgresql.org/message-id/[email protected]...
[2] https://www.postgresql.org/message-id/2901865.1667685211%40sss.pgh.pa.us





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-16 11:44  Alvaro Herrera <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 0 replies; 73+ messages in thread

From: Alvaro Herrera @ 2022-11-16 11:44 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Zhihong Yu <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-Nov-10, Alvaro Herrera wrote:

> I couldn't find any cross-check that
> some perminfo element that we obtain for a rte does actually match the
> relation we wanted to check.  Maybe we could add a test in some central
> place that perminfo->relid equals rte->relid?

I hadn't looked hard enough.  This is already in GetRTEPermissionInfo().


> A related point is that concatenating lists doesn't seem to worry about
> not processing one element multiple times and ending up with bogus offsets.

> I think the API of ConcatRTEPermissionInfoLists is a bit weird.  Why not
> have the function return the resulting list instead, just like
> list_append?  It is more verbose, but it seems easier to grok.

Another point related to this.  I noticed that everyplace we do
ConcatRTEPermissionInfoLists, it is followed by list_append'ing the RT
list themselves.  This is strange.  Maybe that's the wrong way to look
at this, and instead we should have a function that does both things
together: pass both rtables and rtepermlists and smash them all
together.

I attach your 0001 again with a bunch of other fixups (I don't include
your 0002ff).  I've pushed this to see the CI results, and so far it's
looking good (hasn't finished yet though):
https://cirrus-ci.com/build/5126818977021952

I'll have a look at 0002 now.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-21 12:03  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 2 replies; 73+ messages in thread

From: Amit Langote @ 2022-11-21 12:03 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi Alvaro,

On Thu, Nov 10, 2022 at 8:58 PM Alvaro Herrera <[email protected]> wrote:
> Hello
>
> I've been trying to understand what 0001 does.

Thanks a lot for taking a look.

> A related point is that concatenating lists doesn't seem to worry about
> not processing one element multiple times and ending up with bogus offsets.

Could you please clarify what you mean by "an element" here?  Are you
are implying that any given relation, no matter how many times it
occurs in a query (possibly via view rule expansion), should end up
with only one RTEPermissionInfo node covering all its occurrences in
the combined/final rtepermlist, as a result of concatenation/merging
of rtepermlists of different Queries?  Versions of the patch prior to
v17 did it that way, but Tom didn't like that approach much [1], so I
switched to the current implementation where the merging of two
Queries' range tables using list_concat() is accompanied by the
merging of their rtepermlists, again, using list_concat().  So, if the
same table has multiple RTEs in a query, so will there be multiple
corresponding RTEPermissionInfos.

> (I suppose you still have to let an element be processed multiple times
> in case you have nested subqueries?  I wonder how good is the test
> coverage for such scenarios.)

ISTM the existing tests cover a good portion of the changes being made
here, but I guess I'm only saying that because I have spent a
non-trivial amount of time debugging the test failures across many
files over different versions of the patch, especially those that
involve views.

Do you think it might be better to add some new tests as part of this
patch than simply relying on the existing tests not failing?

> Why do callers of add_rte_to_flat_rtable() have to modify the rte's
> perminfoindex themselves, instead of having the function do it for them?
> That looks strange.  But also it's odd that flatten_unplanned_rtes
> concatenates the two lists after all the perminfoindexes have been
> modified, rather than doing both things (adding each RTEs perminfo to
> the global list and offsetting the index) as we walk the list, in
> flatten_rtes_walker.  It looks like these two actions are disconnected
> from one another, but unless I misunderstand, in reality the opposite is
> true.

OK, I have moved the step of updating perminfoindex into
add_rte_to_flat_rtable(), where it looks up the RTEPermissionInfo for
an RTE being added using GetRTEPermissionInfo() and lappend()'s it to
finalrtepermlist before updating the index.  For flatten_rtes_walker()
then to rely on that facility, I needed to make some changes to its
arguments to pass the correct Query node to pick the rtepermlist from.
Overall, setrefs.c changes now hopefully look saner than in the last
version.

As soon as I made that change, I noticed a bunch of ERRORs in
regression tests due to the checks in GetRTEPermissionInfo(), though
none that looked like live bugs.  Though I did find some others as I
was reworking the code to fix those errors, which I have fixed too.

> I think the API of ConcatRTEPermissionInfoLists is a bit weird.  Why not
> have the function return the resulting list instead, just like
> list_append?  It is more verbose, but it seems easier to grok.

Agreed, I have merged your delta patch into 0001.

On Wed, Nov 16, 2022 at 8:44 PM Alvaro Herrera <[email protected]> wrote:
> > A related point is that concatenating lists doesn't seem to worry about
> > not processing one element multiple times and ending up with bogus offsets.
>
> > I think the API of ConcatRTEPermissionInfoLists is a bit weird.  Why not
> > have the function return the resulting list instead, just like
> > list_append?  It is more verbose, but it seems easier to grok.
>
> Another point related to this.  I noticed that everyplace we do
> ConcatRTEPermissionInfoLists, it is followed by list_append'ing the RT
> list themselves.  This is strange.  Maybe that's the wrong way to look
> at this, and instead we should have a function that does both things
> together: pass both rtables and rtepermlists and smash them all
> together.

OK, how does the attached 0002 look in that regard?  In it, I have
renamed ConcatRTEPermissionInfoLists() to CombineRangeTables() which
does all that.  Though, given the needs of rewriteRuleAction(), the
API of it may look a bit weird.  (Only posting it separately for the
ease of comparison.)

> I attach your 0001 again with a bunch of other fixups (I don't include
> your 0002ff).  I've pushed this to see the CI results, and so far it's
> looking good (hasn't finished yet though):
> https://cirrus-ci.com/build/5126818977021952

I have merged all.

While working on these changes, I realized that 0002 (the patch to
remove OLD/NEW RTEs from the stored view query's range table) was
going a bit too far by removing UpdateRangeTableOfViewParse()
altogether.  You may have noticed that a RTE_RELATION entry for the
view relation is needed anyway for permission checking, locking, etc.
and the patch was making the rewriter add one explicitly, whereas the
OLD RTE would be playing that role previously.  In the updated
version, I have decided to keep UpdateRangeTableOfViewParse() while
removing the code in it that adds the NEW RTE, which is totally
unnecessary.  Also removed the rewriter changes that were needed
previously.  Most of the previous version of the patch was a whole
bunch of regression test output changes, because the stored view
query's range table would be changed such that deparsed version of
those queries need no longer qualify output columns (only 1 entry in
the range table in those cases), though I didn't necessarily think
that that looked better.  In the new version, because the stored view
query contains the OLD entry, those qualifications stay, and so none
of the regression test changes are necessary anymore.  postgres_fdw
ones are unrelated and noted in the commit message.

[1] https://www.postgresql.org/message-id/3094251.1658955855%40sss.pgh.pa.us

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v26-0003-Do-not-add-the-NEW-entry-to-view-rule-action-s-r.patch (10.3K, ../../CA+HiwqH2_EiCduPs2HJXu=GrRYWh-UacFp6dPg7zu7XaADg4kQ@mail.gmail.com/2-v26-0003-Do-not-add-the-NEW-entry-to-view-rule-action-s-r.patch)
  download | inline diff:
From 36cf9b37e3c807d037b596c6193d93234284aa94 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Mon, 21 Nov 2022 15:27:56 +0900
Subject: [PATCH v26 3/4] Do not add the NEW entry to view rule action's range
 table

The OLD entry suffices as a placeholder for the view relation when
it is queried, such as checking its permissions during a query's
execution, but the NEW entry has no role to play whatsoever.

Because there are now fewer entries in the view query's range table,
this change affects how the deparsed queries look, where the output
of deparsing (such as alias names) depends on using RT indexs and
the original query references a view.  To wit, some postgres_fdw
regression tests whose expected output changes as a result of this
have been are updated to match.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  4 +-
 src/backend/commands/lockcmds.c               |  6 +-
 src/backend/commands/view.c                   | 74 +++++++------------
 src/backend/rewrite/rewriteDefine.c           |  6 +-
 src/backend/rewrite/rewriteHandler.c          |  4 +-
 5 files changed, 35 insertions(+), 59 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 558e94b845..a84176cf65 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r7 ON (((r5.c1 = r7.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..ce0e6ac112 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -195,12 +195,10 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char	   *relname = get_rel_name(relid);
 
 			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
+			 * The OLD placeholder entry in the view's rtable is skipped.
 			 */
 			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
+				(strcmp(rte->eref->aliasname, "old") == 0))
 				continue;
 
 			/* Currently, we only allow plain tables or views to be locked. */
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6bb707fd51..347c797b58 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -356,29 +356,25 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 /*---------------------------------------------------------------
  * UpdateRangeTableOfViewParse
  *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
+ * Update the range table of the given parsetree to add a placeholder entry
+ * for the view relation and increase the 'varnos' of all the Var nodes
+ * by 1 to account for its addition.
  *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
+ * This extra RT entry for the view relation is not actually used in the query
+ * but it is needed so that 1) the executor can checks the permissions via the
+ * RTEPermissionInfo that is also added in this function, 2) the executor can
+ * lock the view, and 3) the planner can record the view's OID in
+ * PlannedStmt.relationOids such that any concurrent changes to its schema
+ * would invlidate the plans refencing the view.
  *---------------------------------------------------------------
  */
 static Query *
 UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 {
 	Relation	viewRel;
-	List	   *new_rt;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	RTEPermissionInfo *rte_perminfo1;
+	RangeTblEntry *rt_entry;
+	RTEPermissionInfo *rte_perminfo;
 	ParseState *pstate;
 	ListCell   *lc;
 
@@ -399,31 +395,25 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	viewRel = relation_open(viewOid, AccessShareLock);
 
 	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
+	 * Create the new range table entry and form the new range table where
+	 * the OLD entry is added first, followed by the entries in the view
+	 * query's range table.
 	 */
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("old", NIL),
 										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	rte_perminfo1 = nsitem->p_perminfo;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
+	rt_entry = nsitem->p_rte;
+	rte_perminfo = nsitem->p_perminfo;
 
 	/*
-	 * Add only the "old" RTEPermissionInfo at the head of view query's list
-	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
-	 * query on the view, ApplyRetrieveRule() will transfer the view relation's
-	 * permission details into this RTEPermissionInfo.  That's needed because
-	 * the view's RTE itself will be transposed into a subquery RTE that can't
-	 * carry the permission details; see the code stanza toward the end of
-	 * ApplyRetrieveRule() for how that's done.
+	 * When rewriting a query on the view, ApplyRetrieveRule() will transfer
+	 * the view relation's permission details into this RTEPermissionInfo.
+	 * That's needed because the view's RTE itself will be transposed into a
+	 * subquery RTE that can't carry the permission details; see the code
+	 * stanza toward the end of ApplyRetrieveRule() for how that's done.
 	 */
-	viewParse->rtepermlist = lcons(rte_perminfo1, viewParse->rtepermlist);
+	viewParse->rtepermlist = lcons(rte_perminfo, viewParse->rtepermlist);
 	foreach(lc, viewParse->rtable)
 	{
 		RangeTblEntry *rte = lfirst(lc);
@@ -431,22 +421,12 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 		if (rte->perminfoindex > 0)
 			rte->perminfoindex += 1;
 	}
-	/*
-	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is bit
-	 * of a hack given that all the non-child RTE_RELATION entries really
-	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
-	 * go away anyway in the very near future.
-	 */
-	rt_entry2->perminfoindex = 0;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
+	viewParse->rtable = lcons(rt_entry, viewParse->rtable);
 
 	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
+	 * Now offset all var nodes by 1, and jointree RT indexes too.
 	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
+	OffsetVarNodes((Node *) viewParse, 1, 0);
 
 	relation_close(viewRel, AccessShareLock);
 
@@ -616,8 +596,8 @@ void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
 	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
+	 * Add a placeholder entry for the "OLD" relation to the range table of
+	 * 'viewParse'; see the header comment for why it's needed.
 	 */
 	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 3b2649f7a0..dd31bc90ae 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -801,10 +801,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
  * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
+ * expanded.  However, for other types of rules it's important to set these
+ * fields to match the rule owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 971e273681..06f026263b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1902,8 +1902,8 @@ ApplyRetrieveRule(Query *parsetree,
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
  * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
+ * OLD rel for updating.  The best way to handle that seems to be to scan
+ * the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
-- 
2.35.3



  [application/octet-stream] v26-0002-ConcatRTEPermissionInfoLists-CombineRangeTable.patch (8.5K, ../../CA+HiwqH2_EiCduPs2HJXu=GrRYWh-UacFp6dPg7zu7XaADg4kQ@mail.gmail.com/3-v26-0002-ConcatRTEPermissionInfoLists-CombineRangeTable.patch)
  download | inline diff:
From 1b83e9675caf8002c7d59bcf1f4a76f39c233e6d Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 18 Nov 2022 20:45:06 +0900
Subject: [PATCH v26 2/4] ConcatRTEPermissionInfoLists() -> CombineRangeTable()

---
 src/backend/optimizer/plan/subselect.c    | 13 ++++------
 src/backend/optimizer/prep/prepjointree.c | 28 ++++++++--------------
 src/backend/parser/parse_relation.c       |  2 +-
 src/backend/rewrite/rewriteHandler.c      | 19 ++++++++-------
 src/backend/rewrite/rewriteManip.c        | 29 ++++++++++++++---------
 src/include/rewrite/rewriteManip.h        |  6 ++---
 6 files changed, 47 insertions(+), 50 deletions(-)

diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index cbeb0191fb..14e67d8c4c 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1497,15 +1497,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 		return NULL;
 
 	/*
-	 * Add subquery's RTEPermissionInfos into the upper query.  This also
-	 * updates the subquery's RTEs' perminfoindex.
+	 * Now we can attach the modified subquery rtable to the parent.
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
-													  subselect->rtepermlist,
-													  subselect->rtable);
-
-	/* Now we can attach the modified subquery rtable to the parent */
-	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	parse->rtable = CombineRangeTables(parse->rtable, subselect->rtable, false,
+									   subselect->rtepermlist,
+									   &parse->rtepermlist);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 8ac0a41d0e..a608a4e071 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1198,21 +1198,16 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
-	/*
-	 * Add subquery's RTEPermissionInfos into the upper query.  This also
-	 * updates the subquery's RTEs' perminfoindex.
-	 */
-	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
-													  subquery->rtepermlist,
-													  subquery->rtable);
-
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
 	 * code on the subquery ones too.)
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtable = list_concat(parse->rtable, subquery->rtable);
-
+	parse->rtable = CombineRangeTables(parse->rtable, subquery->rtable, false,
+									   subquery->rtepermlist,
+									   &parse->rtepermlist);
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1346,17 +1341,14 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
-	/*
-	 * Add subquery's RTEPermissionInfos into the upper query.  This also
-	 * updates the subquery's RTEs' perminfoindex.
-	 */
-	root->parse->rtepermlist = ConcatRTEPermissionInfoLists(root->parse->rtepermlist,
-															subquery->rtepermlist,
-															rtable);
 	/*
 	 * Append child RTEs to parent rtable.
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->rtable = CombineRangeTables(root->parse->rtable, rtable,
+											 false, subquery->rtepermlist,
+											 &root->parse->rtepermlist);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 177558a158..40ce0dc308 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -3757,7 +3757,7 @@ GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
 	perminfo = list_nth_node(RTEPermissionInfo, rtepermlist,
 							 rte->perminfoindex - 1);
 	if (perminfo->relid != rte->relid)
-		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match provided RTE (with relid=%u)",
 			 rte->perminfoindex, perminfo->relid, rte->relid);
 
 	return perminfo;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index dde9788755..971e273681 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,7 +353,6 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
-	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -417,15 +416,17 @@ rewriteRuleAction(Query *parsetree,
 	 * NOTE: because planner will destructively alter rtable and rtepermlist,
 	 * we must ensure that rule action's lists are separate and shares no
 	 * substructure with the main query's lists.  Hence do a deep copy here
-	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
-	 * because perminfoindex of those RTEs will be updated there.
+	 * for both.
+	 *
+	 * NB: Must pass parstree->rtable as the src_rtable so that its
+	 * RTE's perminfoindex are updated, though they must appear before the
+	 * sub_action's RTEs, so pass true for 'prepend'.
 	 */
-	query_rtable = copyObject(parsetree->rtable);
-	sub_action->rtepermlist =
-		ConcatRTEPermissionInfoLists(copyObject(sub_action->rtepermlist),
-									 parsetree->rtepermlist,
-									 query_rtable);
-	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
+	sub_action->rtable = CombineRangeTables(sub_action->rtable,
+											copyObject(parsetree->rtable),
+											true,
+											copyObject(parsetree->rtepermlist),
+											&sub_action->rtepermlist);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 8f6446a435..a2090f1d73 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1533,31 +1533,38 @@ ReplaceVarsFromTargetList(Node *node,
 }
 
 /*
- * ConcatRTEPermissionInfoLists
- * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ * ConcatRangeTables
+ * 		Adds the RTEs of 'src_rtable' into 'dest_rtable'
  *
- * Also updates perminfoindex of the RTEs in src_rtable to point to the
- * "source" perminfos after they have been added into *dest_rtepermlist.
+ * If 'prepend' is true, they are added at the head of the list.
+ *
+ * This also adds the RTEPermissionInfos of 'rtepermlist2' (belonging to the
+ * RTEs in 'rtable2') into *rtepermlist1 and updates perminfoindex of the RTEs
+ * in src_rtable to now point to their perminfos' indexes in
+ * *dest_rtepermlist.
  */
 List *
-ConcatRTEPermissionInfoLists(List *dest_rtepermlist, List *src_rtepermlist,
-							 List *src_rtable)
+CombineRangeTables(List *dest_rtable, List *src_rtable, bool prepend,
+				   List *src_rtepermlist, List **dest_rtepermlist)
 {
 	ListCell   *l;
-	int			offset = list_length(dest_rtepermlist);
+	int			permlist_offset = list_length(*dest_rtepermlist);
 
-	dest_rtepermlist = list_concat(dest_rtepermlist, src_rtepermlist);
+	*dest_rtepermlist = list_concat(*dest_rtepermlist, src_rtepermlist);
 
-	if (offset > 0)
+	if (permlist_offset > 0)
 	{
 		foreach(l, src_rtable)
 		{
 			RangeTblEntry  *rte = lfirst_node(RangeTblEntry, l);
 
 			if (rte->perminfoindex > 0)
-				rte->perminfoindex += offset;
+				rte->perminfoindex += permlist_offset;
 		}
 	}
 
-	return dest_rtepermlist;
+	if (prepend)
+		return list_concat(src_rtable, dest_rtable);
+
+	return list_concat(dest_rtable, src_rtable);
 }
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index 76699c8e13..8987c366f7 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,8 +83,8 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
-extern List *ConcatRTEPermissionInfoLists(List *dest_rtepermlist,
-							 List *src_rtepermlist,
-							 List *src_rtable);
+extern List *CombineRangeTables(List *dest_rtable, List *src_rtable,
+				   bool prepend, List *src_rtepermlist,
+				   List **dest_rtepermlist);
 
 #endif							/* REWRITEMANIP_H */
-- 
2.35.3



  [application/octet-stream] v26-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (25.5K, ../../CA+HiwqH2_EiCduPs2HJXu=GrRYWh-UacFp6dPg7zu7XaADg4kQ@mail.gmail.com/4-v26-0004-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 0d05fe4117667f5588ee79e023aa1ca85fc0b8be Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v26 4/4] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++---
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 38 +++++++++++++++++++
 src/backend/optimizer/prep/preptlist.c   | 47 ++++++++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 22 +++++------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 -----------------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 129 insertions(+), 74 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index e2344127da..d60e36db85 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1420,20 +1420,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b7ea953b55..abf681f401 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3965,6 +3965,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 59b0fdeb62..2d369e0340 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -561,7 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 966b75f5a6..d720aea857 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -537,7 +537,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5013ac3377..2360ae280b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5cddc9d6cf..4178eb416d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,35 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+						{
+							extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+															  extraUpdatedCols);
+						}
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1890,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						/* See the comment in the inherited UPDATE block. */
+						if (root->extraUpdatedCols)
+						{
+							root->extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+						}
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1912,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE/MERGE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					/* See the comment in the inherited UPDATE block. */
+					if (root->extraUpdatedCols)
+					{
+						root->extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1959,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..6bbcb6ac1d 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,41 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 09e2ffdfbc..ecfa179bfc 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -149,6 +150,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -314,6 +316,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -344,7 +347,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -413,14 +416,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -562,13 +569,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -875,7 +875,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
+Bitmapset *
 translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
 							   RelOptInfo *top_parent_rel,
 							   Bitmapset *top_parent_cols)
@@ -949,12 +949,12 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
 													 perminfo->updatedCols);
 		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
-														  rte->extraUpdatedCols);
+														  root->extraUpdatedCols);
 	}
 	else
 	{
 		updatedCols = perminfo->updatedCols;
-		extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
 	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 6dd11329fb..5b489da57d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3644,6 +3644,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3659,6 +3661,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3723,6 +3726,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 568344cc23..eb18a04908 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1815,7 +1816,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1864,7 +1864,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1882,7 +1881,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 06f026263b..470b677fcb 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1628,46 +1628,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3718,7 +3678,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3730,7 +3689,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3815,9 +3773,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c32834a9e8..a85570b1de 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index abcf8ee229..2fe26fbed7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1153,7 +1153,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1ef2f89782..23e1cfc2fb 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2248,6 +2250,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e3a5233dd7..2e7b9a289d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



  [application/octet-stream] v26-0001-Rework-query-relation-permission-checking.patch (151.3K, ../../CA+HiwqH2_EiCduPs2HJXu=GrRYWh-UacFp6dPg7zu7XaADg4kQ@mail.gmail.com/5-v26-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From eedb79bc42b78a2e08fe4f00f8841c3433cfc6b2 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v26 1/4] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  51 ++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |  32 ++-
 src/backend/executor/execMain.c               | 116 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 175 +++++++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  67 +++++--
 src/backend/optimizer/plan/subselect.c        |   8 +
 src/backend/optimizer/prep/prepjointree.c     |  22 ++-
 src/backend/optimizer/util/inherit.c          | 175 +++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  64 ++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 178 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 185 +++++++++---------
 src/backend/rewrite/rewriteManip.c            |  30 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  13 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   3 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 54 files changed, 1046 insertions(+), 565 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d7500abfb..0f0e864c87 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -459,7 +460,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +626,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +659,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1511,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1811,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1901,6 +1902,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1910,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1934,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1946,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2154,6 +2159,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2231,6 +2237,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = GetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2247,7 +2255,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2633,7 +2642,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2653,13 +2661,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3962,12 +3969,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3979,12 +3986,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..3509358fbe 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..6e7f96e7db 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..5ae68842df 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a079c70152..03f8ec459a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 91cee27743..b5b860c3ab 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1290,7 +1290,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 845208d662..10c1955884 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10022,7 +10025,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10219,7 +10223,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12335,7 +12340,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18043,7 +18049,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18981,7 +18988,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6bb707fd51 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
+	RTEPermissionInfo *rte_perminfo1;
 	ParseState *pstate;
+	ListCell   *lc;
 
 	/*
 	 * Make a copy of the given parsetree.  It's not so much that we don't
@@ -405,15 +407,37 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   makeAlias("old", NIL),
 										   false, false);
 	rt_entry1 = nsitem->p_rte;
+	rte_perminfo1 = nsitem->p_perminfo;
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("new", NIL),
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
+	/*
+	 * Add only the "old" RTEPermissionInfo at the head of view query's list
+	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
+	 * query on the view, ApplyRetrieveRule() will transfer the view relation's
+	 * permission details into this RTEPermissionInfo.  That's needed because
+	 * the view's RTE itself will be transposed into a subquery RTE that can't
+	 * carry the permission details; see the code stanza toward the end of
+	 * ApplyRetrieveRule() for how that's done.
+	 */
+	viewParse->rtepermlist = lcons(rte_perminfo1, viewParse->rtepermlist);
+	foreach(lc, viewParse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += 1;
+	}
+	/*
+	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is bit
+	 * of a hack given that all the non-child RTE_RELATION entries really
+	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
+	 * go away anyway in the very near future.
+	 */
+	rt_entry2->perminfoindex = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..d4e90370f7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -54,6 +54,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -74,8 +75,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +91,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +555,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +566,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +656,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +688,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +704,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +764,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +803,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1847,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1932,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1985,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2093,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..e2344127da 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,106 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * ExecGetRTEPermissionInfo
+ *		Returns the RTEPermissionInfo contained in estate->es_rtepermlist
+ *		pointed to by the RTE
+ */
+RTEPermissionInfo *
+ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate)
+{
+	Assert(estate->es_rtepermlist != NIL);
+	return GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+}
+
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+Oid
+GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = ExecGetRTEPermissionInfo(rte, estate);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1360,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = ExecGetRTEPermissionInfo(rte, estate);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f05e72f0dc..59b0fdeb62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -507,6 +507,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -560,11 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 23776367c5..966b75f5a6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -473,6 +473,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -536,11 +537,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ac86ce9003..5013ac3377 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 493a3af0fa..5cddc9d6cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6265,6 +6269,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6392,6 +6397,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..d88fa0b2a8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -78,6 +79,13 @@ typedef struct
 	int			newvarno;
 } fix_windowagg_cond_context;
 
+/* Context info for flatten_rtes_walker() */
+typedef struct
+{
+	PlannerGlobal  *glob;
+	Query		   *query;
+} flatten_rtes_walker_context;
+
 /*
  * Selecting the best alternative in an AlternativeSubPlan expression requires
  * estimating how many times that expression will be evaluated.  For an
@@ -113,8 +121,9 @@ typedef struct
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
-static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
+static bool flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt);
+static void add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
+								   RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 										  IndexOnlyScan *plan,
@@ -355,6 +364,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -375,7 +387,7 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(glob, root->parse->rtepermlist, rte);
 	}
 
 	/*
@@ -442,18 +454,21 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 /*
  * Extract RangeTblEntries from a subquery that was never planned at all
  */
+
 static void
 flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 {
+	flatten_rtes_walker_context cxt = {glob, rte->subquery};
+
 	/* Use query_tree_walker to find all RTEs in the parse tree */
 	(void) query_tree_walker(rte->subquery,
 							 flatten_rtes_walker,
-							 (void *) glob,
+							 (void *) &cxt,
 							 QTW_EXAMINE_RTES_BEFORE);
 }
 
 static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
+flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt)
 {
 	if (node == NULL)
 		return false;
@@ -463,33 +478,39 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(cxt->glob, cxt->query->rtepermlist, rte);
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into subselects */
+		/*
+		 * Recurse into subselects.  Must update cxt->query to this query so
+		 * that the rtable and rtepermlist correspond with each other.
+		 */
+		cxt->query = (Query *) node;
 		return query_tree_walker((Query *) node,
 								 flatten_rtes_walker,
-								 (void *) glob,
+								 (void *) cxt,
 								 QTW_EXAMINE_RTES_BEFORE);
 	}
 	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+								  (void *) cxt);
 }
 
 /*
- * Add (a copy of) the given RTE to the final rangetable
+ * Add (a copy of) the given RTE to the final rangetable and also the
+ * corresponding RTEPermissionInfo, if any, to final rtepermlist.
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
-add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
+add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
+					   RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
 
@@ -526,7 +547,23 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 * but it would probably cost more cycles than it would save.
 	 */
 	if (newrte->rtekind == RTE_RELATION)
+	{
+		RTEPermissionInfo *perminfo;
+
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+		/*
+		 * Add the RTEPermissionInfo, if any, corresponding to this RTE to
+		 * the flattened global list.  Also update the perminfoindex to
+		 * reflect the RTEPermissionInfo's new position.
+		 */
+		if (rte->perminfoindex > 0)
+		{
+			perminfo = GetRTEPermissionInfo(rtepermlist, newrte);
+			glob->finalrtepermlist = lappend(glob->finalrtepermlist, perminfo);
+			newrte->perminfoindex = list_length(glob->finalrtepermlist);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..cbeb0191fb 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,14 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
+													  subselect->rtepermlist,
+													  subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index f4cdb879c2..8ac0a41d0e 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
+													  subquery->rtepermlist,
+													  subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1346,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	root->parse->rtepermlist = ConcatRTEPermissionInfoLists(root->parse->rtepermlist,
+															subquery->rtepermlist,
+															rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..09e2ffdfbc 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +313,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +333,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +410,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +469,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,9 +492,16 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
+	/*
+	 * No permission checking for the child RTE unless it's the parent
+	 * relation in its child role, which only applies to traditional
+	 * inheritance.
+	 */
+	if (childOID != parentOID)
+		childrte->perminfoindex = 0;
+
 	/* Link not-yet-fully-filled child RTE into data structures */
 	parse->rtable = lappend(parse->rtable, childrte);
 	childRTindex = list_length(parse->rtable);
@@ -539,33 +562,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +868,94 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index	use_relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  rte->extraUpdatedCols);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = rte->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7b4434e7f..a1dedf52d5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -223,7 +224,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo;
+
+			perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..5279866f43 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 7913523b1c..79171fb725 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -214,6 +214,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -286,7 +287,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -345,7 +346,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -359,8 +360,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..177558a158 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1021,11 +1021,15 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo;
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo = GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
-										   col - FirstLowInvalidHeapAttributeNumber);
+		perminfo->selectedCols =
+			bms_add_member(perminfo->selectedCols,
+						   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
@@ -1235,10 +1239,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1274,6 +1281,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1417,6 +1425,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1453,7 +1462,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1462,12 +1471,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1481,7 +1486,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1518,6 +1523,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1541,7 +1547,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1550,12 +1556,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1569,7 +1571,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1643,21 +1645,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1974,20 +1970,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1999,7 +1988,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2066,20 +2055,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2154,19 +2136,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2251,19 +2227,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2278,6 +2248,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2401,19 +2372,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2527,16 +2492,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2548,7 +2510,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3173,6 +3135,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3190,7 +3153,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3742,3 +3708,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index (1-based!) */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	if (rte->perminfoindex == 0 ||
+		rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %d in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = list_nth_node(RTEPermissionInfo, rtepermlist,
+							 rte->perminfoindex - 1);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match requested RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index f54591a987..654a94c2f7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 487eb2041b..efff8c03b3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e48a3f589a..568344cc23 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index db45d8a08b..3b2649f7a0 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -797,14 +797,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -831,18 +831,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..dde9788755 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -395,32 +396,36 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	query_rtable = copyObject(parsetree->rtable);
+	sub_action->rtepermlist =
+		ConcatRTEPermissionInfoLists(copyObject(sub_action->rtepermlist),
+									 parsetree->rtepermlist,
+									 query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1629,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1658,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1743,6 +1751,8 @@ ApplyRetrieveRule(Query *parsetree,
 	Query	   *rule_action;
 	RangeTblEntry *rte,
 			   *subrte;
+	RTEPermissionInfo *perminfo,
+			   *sub_perminfo;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1793,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1856,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1872,6 +1864,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rte);
 
 	rte->rtekind = RTE_SUBQUERY;
 	rte->subquery = rule_action;
@@ -1881,6 +1874,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;		/* no permission checking for this RTE */
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
@@ -1889,19 +1883,12 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	sub_perminfo = GetRTEPermissionInfo(rule_action->rtepermlist, subrte);
+	sub_perminfo->requiredPerms = perminfo->requiredPerms;
+	sub_perminfo->checkAsUser = perminfo->checkAsUser;
+	sub_perminfo->selectedCols = perminfo->selectedCols;
+	sub_perminfo->insertedCols = perminfo->insertedCols;
+	sub_perminfo->updatedCols = perminfo->updatedCols;
 
 	return parsetree;
 }
@@ -1931,8 +1918,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3064,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3203,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3275,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3431,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3442,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
+		/* Ignore the RTEPermissionInfo that would've been added. */
+		new_exclRte->perminfoindex = 0;
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3717,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3729,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3816,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..8f6446a435 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,33 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+List *
+ConcatRTEPermissionInfoLists(List *dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(dest_rtepermlist);
+
+	dest_rtepermlist = list_concat(dest_rtepermlist, src_rtepermlist);
+
+	if (offset > 0)
+	{
+		foreach(l, src_rtable)
+		{
+			RangeTblEntry  *rte = lfirst_node(RangeTblEntry, l);
+
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += offset;
+		}
+	}
+
+	return dest_rtepermlist;
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc07157037..29f29d664b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e0aeaa6909..95ae311e83 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5158,7 +5158,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5210,7 +5210,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5293,7 +5293,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5341,7 +5341,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5402,6 +5402,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5410,7 +5411,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5479,7 +5480,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..787ad8b232 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..3d2de0ae1b 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..beb40fc9a5 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -576,6 +577,7 @@ exec_rt_fetch(Index rti, EState *estate)
 }
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
+extern RTEPermissionInfo *ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 								   Index rti);
 
@@ -600,7 +602,10 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
+extern Oid GetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate);
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..c32834a9e8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7caff62af7..abcf8ee229 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -966,37 +969,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1052,11 +1024,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the containing query's rtepermlist; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1176,14 +1153,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns they need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a544b313d3..1ef2f89782 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 5c2ab1b379..e3a5233dd7 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -75,6 +75,10 @@ typedef struct PlannedStmt
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	List	   *appendRelations;	/* list of AppendRelInfo nodes */
 
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..76699c8e13 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,8 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern List *ConcatRTEPermissionInfoLists(List *dest_rtepermlist,
+							 List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-21 12:46  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-11-21 12:46 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Nov 21, 2022 at 9:03 PM Amit Langote <[email protected]> wrote:
> On Thu, Nov 10, 2022 at 8:58 PM Alvaro Herrera <[email protected]> wrote:
> > Why do callers of add_rte_to_flat_rtable() have to modify the rte's
> > perminfoindex themselves, instead of having the function do it for them?
> > That looks strange.  But also it's odd that flatten_unplanned_rtes
> > concatenates the two lists after all the perminfoindexes have been
> > modified, rather than doing both things (adding each RTEs perminfo to
> > the global list and offsetting the index) as we walk the list, in
> > flatten_rtes_walker.  It looks like these two actions are disconnected
> > from one another, but unless I misunderstand, in reality the opposite is
> > true.
>
> OK, I have moved the step of updating perminfoindex into
> add_rte_to_flat_rtable(), where it looks up the RTEPermissionInfo for
> an RTE being added using GetRTEPermissionInfo() and lappend()'s it to
> finalrtepermlist before updating the index.  For flatten_rtes_walker()
> then to rely on that facility, I needed to make some changes to its
> arguments to pass the correct Query node to pick the rtepermlist from.
> Overall, setrefs.c changes now hopefully look saner than in the last
> version.
>
> As soon as I made that change, I noticed a bunch of ERRORs in
> regression tests due to the checks in GetRTEPermissionInfo(), though
> none that looked like live bugs.

I figured the no-live-bugs part warrants some clarification.  The
plan-time errors that I saw were caused in many cases by an RTE not
pointing into the correct list or having incorrect perminfoindex, most
or all of those cases involving views.  Passing a wrong perminfoindex
to the executor, though obviously not good and fixed in the latest
version, wasn't a problem in those cases, because none of those tests
would cause the executor to use the perminfoindex, such as by calling
GetRTEPermissionInfo().  I thought about that being problematic in
terms of our coverage of perminfoindex related code in the executor,
but don't really see how we could improve that situation as far as
views are concerned, because the executor is only concerned about
checking permissions for views and perminfoindex is irrelevant in that
path, because the RTEPermissionInfos are accessed directly from
es_rtepermlist.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-25 11:28  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-11-25 11:28 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Nov 21, 2022 at 9:03 PM Amit Langote <[email protected]> wrote:
> On Wed, Nov 16, 2022 at 8:44 PM Alvaro Herrera <[email protected]> wrote:
> > > A related point is that concatenating lists doesn't seem to worry about
> > > not processing one element multiple times and ending up with bogus offsets.
> >
> > > I think the API of ConcatRTEPermissionInfoLists is a bit weird.  Why not
> > > have the function return the resulting list instead, just like
> > > list_append?  It is more verbose, but it seems easier to grok.
> >
> > Another point related to this.  I noticed that everyplace we do
> > ConcatRTEPermissionInfoLists, it is followed by list_append'ing the RT
> > list themselves.  This is strange.  Maybe that's the wrong way to look
> > at this, and instead we should have a function that does both things
> > together: pass both rtables and rtepermlists and smash them all
> > together.
>
> OK, how does the attached 0002 look in that regard?  In it, I have
> renamed ConcatRTEPermissionInfoLists() to CombineRangeTables() which
> does all that.  Though, given the needs of rewriteRuleAction(), the
> API of it may look a bit weird.  (Only posting it separately for the
> ease of comparison.)

Here's a revised version in which I've revised the code near the call
site of CombineRangeTables() in rewriteRuleAction() such that the
weirdness of that API in the last version becomes unnecessary.  When
doing those changes, I realized that we perhaps need some new tests to
exercise rewriteRuleAction(), especially to test the order of checking
permissions present in the (combined) range table of rewritten action
query, though I have not added them yet.

I've included a new patch (0002) that I've also posted at [1] for this
patch set to compile/work.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

[1] https://www.postgresql.org/message-id/CA%2BHiwqHbv4xQd-yHx0LWA04AybA%2BGQPy66UJxt8m32gB6zCYQQ%40mail...


Attachments:

  [application/octet-stream] v27-0002-Fix-AclMode-node-support.patch (1.0K, ../../CA+HiwqHQeooqvXy+FSXYOq0wajGtC5-TBj0=ZW1=qVq5Pm2ycw@mail.gmail.com/2-v27-0002-Fix-AclMode-node-support.patch)
  download | inline diff:
From a87b17e2b2f7f265e49d00cedd2221ed440d5b37 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 25 Nov 2022 16:21:19 +0900
Subject: [PATCH v27 2/5] Fix AclMode node support

---
 src/backend/nodes/gen_node_support.pl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index d3f25299de..b6f086e262 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -954,7 +954,6 @@ _read${n}(void)
 		}
 		elsif ($t eq 'uint32'
 			|| $t eq 'bits32'
-			|| $t eq 'AclMode'
 			|| $t eq 'BlockNumber'
 			|| $t eq 'Index'
 			|| $t eq 'SubTransactionId')
@@ -962,7 +961,8 @@ _read${n}(void)
 			print $off "\tWRITE_UINT_FIELD($f);\n";
 			print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read;
 		}
-		elsif ($t eq 'uint64')
+		elsif ($t eq 'uint64'
+			|| $t eq 'AclMode')
 		{
 			print $off "\tWRITE_UINT64_FIELD($f);\n";
 			print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read;
-- 
2.35.3



  [application/octet-stream] v27-0001-Rework-query-relation-permission-checking.patch (151.6K, ../../CA+HiwqHQeooqvXy+FSXYOq0wajGtC5-TBj0=ZW1=qVq5Pm2ycw@mail.gmail.com/3-v27-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 71cb533a4d4cb0242b479ab4361e4bcd6bc4581b Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v27 1/5] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  51 ++---
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 portlock/53981.rsv                            |   1 +
 src/backend/access/common/attmap.c            |  14 +-
 src/backend/access/common/tupconvert.c        |   2 +-
 src/backend/catalog/partition.c               |   3 +-
 src/backend/commands/copy.c                   |  17 +-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/commands/tablecmds.c              |  24 ++-
 src/backend/commands/view.c                   |  32 ++-
 src/backend/executor/execMain.c               | 116 +++++------
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execPartition.c          |  15 +-
 src/backend/executor/execUtils.c              | 175 +++++++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/createplan.c       |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  67 +++++--
 src/backend/optimizer/plan/subselect.c        |   8 +
 src/backend/optimizer/prep/prepjointree.c     |  22 ++-
 src/backend/optimizer/util/inherit.c          | 175 +++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  64 ++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 178 +++++++++--------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   9 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/replication/pgoutput/pgoutput.c   |   2 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 185 +++++++++---------
 src/backend/rewrite/rewriteManip.c            |  30 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/statistics/extended_stats.c       |   7 +-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/adt/selfuncs.c              |  13 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/access/attmap.h                   |   6 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  13 +-
 src/include/nodes/execnodes.h                 |   9 +
 src/include/nodes/parsenodes.h                |  95 +++++----
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   5 +
 src/include/optimizer/inherit.h               |   1 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   3 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 55 files changed, 1047 insertions(+), 565 deletions(-)
 create mode 100644 portlock/53981.rsv

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d7500abfb..ae2c11dc79 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -459,7 +460,8 @@ static PgFdwModifyState *create_foreign_modify(EState *estate,
 											   List *target_attrs,
 											   int values_end,
 											   bool has_returning,
-											   List *retrieved_attrs);
+											   List *retrieved_attrs,
+											   Oid userid);
 static TupleTableSlot **execute_foreign_modify(EState *estate,
 											   ResultRelInfo *resultRelInfo,
 											   CmdType operation,
@@ -624,7 +626,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -658,12 +659,12 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to
+	 * lack of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1511,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckPermissions() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
+
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -1811,7 +1811,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = GetRelAllUpdatedCols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -1901,6 +1902,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 						   int subplan_index,
 						   int eflags)
 {
+	EState	   *estate = mtstate->ps.state;
 	PgFdwModifyState *fmstate;
 	char	   *query;
 	List	   *target_attrs;
@@ -1908,6 +1910,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	int			values_end_len;
 	List	   *retrieved_attrs;
 	RangeTblEntry *rte;
+	Oid			userid;
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1931,6 +1934,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	/* Find RTE. */
 	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
 						mtstate->ps.state);
+	userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -1942,7 +1946,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									target_attrs,
 									values_end_len,
 									has_returning,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	resultRelInfo->ri_FdwState = fmstate;
 }
@@ -2154,6 +2159,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	List	   *targetAttrs = NIL;
 	List	   *retrieved_attrs = NIL;
 	bool		doNothing = false;
+	Oid			userid;
 
 	/*
 	 * If the foreign table we are about to insert routed rows into is also an
@@ -2231,6 +2237,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		rte = exec_rt_fetch(resultRelation, estate);
 	}
 
+	userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
+
 	/* Construct the SQL command string. */
 	deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
 					 resultRelInfo->ri_WithCheckOptions,
@@ -2247,7 +2255,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 									targetAttrs,
 									values_end_len,
 									retrieved_attrs != NIL,
-									retrieved_attrs);
+									retrieved_attrs,
+									userid);
 
 	/*
 	 * If the given resultRelInfo already has PgFdwModifyState set, it means
@@ -2633,7 +2642,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2653,13 +2661,12 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
@@ -3962,12 +3969,12 @@ create_foreign_modify(EState *estate,
 					  List *target_attrs,
 					  int values_end,
 					  bool has_returning,
-					  List *retrieved_attrs)
+					  List *retrieved_attrs,
+					  Oid userid)
 {
 	PgFdwModifyState *fmstate;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
 	AttrNumber	n_params;
@@ -3979,12 +3986,6 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
-
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
 	user = GetUserMapping(userid, table->serverid);
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..3509358fbe 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..6e7f96e7db 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/portlock/53981.rsv b/portlock/53981.rsv
new file mode 100644
index 0000000000..8a1c26310e
--- /dev/null
+++ b/portlock/53981.rsv
@@ -0,0 +1 @@
+    102212
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index 896f82a22b..1e65d8a120 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -169,10 +169,15 @@ build_attrmap_by_position(TupleDesc indesc,
  * and output columns by name.  (Dropped columns are ignored in both input and
  * output.)  This is normally a subroutine for convert_tuples_by_name in
  * tupconvert.c, but can be used standalone.
+ *
+ * If 'missing_ok' is true, a column from 'outdesc' not being present in
+ * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
+ * outdesc column will be 0 in that case.
  */
 AttrMap *
 build_attrmap_by_name(TupleDesc indesc,
-					  TupleDesc outdesc)
+					  TupleDesc outdesc,
+					  bool missing_ok)
 {
 	AttrMap    *attrMap;
 	int			outnatts;
@@ -235,7 +240,7 @@ build_attrmap_by_name(TupleDesc indesc,
 				break;
 			}
 		}
-		if (attrMap->attnums[i] == 0)
+		if (attrMap->attnums[i] == 0 && !missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("could not convert row type"),
@@ -257,12 +262,13 @@ build_attrmap_by_name(TupleDesc indesc,
  */
 AttrMap *
 build_attrmap_by_name_if_req(TupleDesc indesc,
-							 TupleDesc outdesc)
+							 TupleDesc outdesc,
+							 bool missing_ok)
 {
 	AttrMap    *attrMap;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name(indesc, outdesc);
+	attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
 
 	/* Check if the map has a one-to-one match */
 	if (check_attrmap_match(indesc, outdesc, attrMap))
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4010e20cfb..b2f892d2fd 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -107,7 +107,7 @@ convert_tuples_by_name(TupleDesc indesc,
 	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
-	attrMap = build_attrmap_by_name_if_req(indesc, outdesc);
+	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 	if (attrMap == NULL)
 	{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6ec479004..79ccddce55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -227,7 +227,8 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 		bool		found_whole_row;
 
 		part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
-											RelationGetDescr(from_rel));
+											RelationGetDescr(from_rel),
+											false);
 		expr = (List *) map_variable_attnos((Node *) expr,
 											fromrel_varno, 0,
 											part_attmap,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..5ae68842df 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -154,11 +155,13 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 			FirstLowInvalidHeapAttributeNumber;
 
 			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
+				perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+														attno);
 			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+				perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+														attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +177,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a079c70152..03f8ec459a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 91cee27743..b5b860c3ab 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1290,7 +1290,8 @@ DefineIndex(Oid relationId,
 				childidxs = RelationGetIndexList(childrel);
 				attmap =
 					build_attrmap_by_name(RelationGetDescr(childrel),
-										  parentDesc);
+										  parentDesc,
+										  false);
 
 				foreach(cell, childidxs)
 				{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 845208d662..10c1955884 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1206,7 +1206,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			}
 
 			attmap = build_attrmap_by_name(RelationGetDescr(rel),
-										   RelationGetDescr(parent));
+										   RelationGetDescr(parent),
+										   false);
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
@@ -9647,7 +9648,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 * definition to match the partition's column layout.
 			 */
 			map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
-											   RelationGetDescr(pkrel));
+											   RelationGetDescr(pkrel),
+											   false);
 			if (map)
 			{
 				mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
@@ -9814,7 +9816,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			CheckTableNotInUse(partition, "ALTER TABLE");
 
 			attmap = build_attrmap_by_name(RelationGetDescr(partition),
-										   RelationGetDescr(rel));
+										   RelationGetDescr(rel),
+										   false);
 			for (int j = 0; j < numfks; j++)
 				mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
 
@@ -10022,7 +10025,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	trigrel = table_open(TriggerRelationId, RowExclusiveLock);
 
 	attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 	foreach(cell, clone)
 	{
 		Oid			constrOid = lfirst_oid(cell);
@@ -10219,7 +10223,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 	 * different.  This map is used to convert them.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(partRel),
-								   RelationGetDescr(parentRel));
+								   RelationGetDescr(parentRel),
+								   false);
 
 	partFKs = copyObject(RelationGetFKeyList(partRel));
 
@@ -12335,7 +12340,8 @@ ATPrepAlterColumnType(List **wqueue,
 				cmd = copyObject(cmd);
 
 				attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-											   RelationGetDescr(rel));
+											   RelationGetDescr(rel),
+											   false);
 				((ColumnDef *) cmd->def)->cooked_default =
 					map_variable_attnos(def->cooked_default,
 										1, 0,
@@ -18043,7 +18049,8 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 		/* construct an indexinfo to compare existing indexes against */
 		info = BuildIndexInfo(idxRel);
 		attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
-									   RelationGetDescr(rel));
+									   RelationGetDescr(rel),
+									   false);
 		constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
 
 		/*
@@ -18981,7 +18988,8 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		childInfo = BuildIndexInfo(partIdx);
 		parentInfo = BuildIndexInfo(parentIdx);
 		attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
-									   RelationGetDescr(parentTbl));
+									   RelationGetDescr(parentTbl),
+									   false);
 		if (!CompareIndexInfo(childInfo, parentInfo,
 							  partIdx->rd_indcollation,
 							  parentIdx->rd_indcollation,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..6bb707fd51 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
+	RTEPermissionInfo *rte_perminfo1;
 	ParseState *pstate;
+	ListCell   *lc;
 
 	/*
 	 * Make a copy of the given parsetree.  It's not so much that we don't
@@ -405,15 +407,37 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   makeAlias("old", NIL),
 										   false, false);
 	rt_entry1 = nsitem->p_rte;
+	rte_perminfo1 = nsitem->p_perminfo;
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("new", NIL),
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
+	/*
+	 * Add only the "old" RTEPermissionInfo at the head of view query's list
+	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
+	 * query on the view, ApplyRetrieveRule() will transfer the view relation's
+	 * permission details into this RTEPermissionInfo.  That's needed because
+	 * the view's RTE itself will be transposed into a subquery RTE that can't
+	 * carry the permission details; see the code stanza toward the end of
+	 * ApplyRetrieveRule() for how that's done.
+	 */
+	viewParse->rtepermlist = lcons(rte_perminfo1, viewParse->rtepermlist);
+	foreach(lc, viewParse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += 1;
+	}
+	/*
+	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is bit
+	 * of a hack given that all the non-child RTE_RELATION entries really
+	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
+	 * go away anyway in the very near future.
+	 */
+	rt_entry2->perminfoindex = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..d4e90370f7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -54,6 +54,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -74,8 +75,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,8 +91,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
 									  Bitmapset *modifiedCols,
 									  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
@@ -554,8 +555,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +566,63 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down from
+	 * there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +656,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,15 +688,15 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_INSERT && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->insertedCols,
+																	  perminfo->insertedCols,
 																	  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
+		if (remainingPerms & ACL_UPDATE && !ExecCheckPermissionsModified(relOid,
 																	  userid,
-																	  rte->updatedCols,
+																	  perminfo->updatedCols,
 																	  ACL_UPDATE))
 			return false;
 	}
@@ -713,12 +704,12 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
 						  AclMode requiredPerms)
 {
 	int			col = -1;
@@ -773,17 +764,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +803,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->rtepermlist, true);
+	estate->es_rtepermlist = plannedstmt->rtepermlist;
 
 	/*
 	 * initialize the node's execution state
@@ -1858,7 +1847,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 
 		old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 		/* a reverse map */
-		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc);
+		map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc, false);
 
 		/*
 		 * Partition-specific slot's tupdesc can't be changed, so allocate a
@@ -1943,7 +1932,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 					/* a reverse map */
 					map = build_attrmap_by_name_if_req(orig_tupdesc,
-													   tupdesc);
+													   tupdesc,
+													   false);
 
 					/*
 					 * Partition-specific slot's tupdesc can't be changed, so
@@ -1995,7 +1985,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 				/* a reverse map */
 				map = build_attrmap_by_name_if_req(old_tupdesc,
-												   tupdesc);
+												   tupdesc,
+												   false);
 
 				/*
 				 * Partition-specific slot's tupdesc can't be changed, so
@@ -2102,7 +2093,8 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
 						/* a reverse map */
 						map = build_attrmap_by_name_if_req(old_tupdesc,
-														   tupdesc);
+														   tupdesc,
+														   false);
 
 						/*
 						 * Partition-specific slot's tupdesc can't be changed,
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..c1c5439fa1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->rtepermlist = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 40e3c07693..b7b57ec404 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -582,7 +582,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		part_attmap =
 			build_attrmap_by_name(RelationGetDescr(partrel),
-								  RelationGetDescr(firstResultRel));
+								  RelationGetDescr(firstResultRel),
+								  false);
 		wcoList = (List *)
 			map_variable_attnos((Node *) wcoList,
 								firstVarno, 0,
@@ -639,7 +640,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 		returningList = (List *)
 			map_variable_attnos((Node *) returningList,
 								firstVarno, 0,
@@ -780,7 +782,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 				if (part_attmap == NULL)
 					part_attmap =
 						build_attrmap_by_name(RelationGetDescr(partrel),
-											  RelationGetDescr(firstResultRel));
+											  RelationGetDescr(firstResultRel),
+											  false);
 				onconflset = (List *)
 					map_variable_attnos((Node *) onconflset,
 										INNER_VAR, 0,
@@ -878,7 +881,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		if (part_attmap == NULL)
 			part_attmap =
 				build_attrmap_by_name(RelationGetDescr(partrel),
-									  RelationGetDescr(firstResultRel));
+									  RelationGetDescr(firstResultRel),
+									  false);
 
 		if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
 			ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
@@ -1140,7 +1144,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		 * routing.
 		 */
 		pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(parent_pd->reldesc),
-												  tupdesc);
+												  tupdesc,
+												  false);
 		pd->tupslot = pd->tupmap ?
 			MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual) : NULL;
 	}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..51fa9a085e 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -1251,33 +1252,106 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
-/* Return a bitmap representing columns being inserted */
-Bitmapset *
-ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  Note that a NULL result is valid and
+ * means that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent, something that's allowed with
+			 * traditional inheritance, are ignored.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
 	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
+/*
+ * ExecGetRTEPermissionInfo
+ *		Returns the RTEPermissionInfo contained in estate->es_rtepermlist
+ *		pointed to by the RTE
+ */
+RTEPermissionInfo *
+ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate)
+{
+	Assert(estate->es_rtepermlist != NIL);
+	return GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+}
+
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+Oid
+ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+
+	/*
+	 * For inheritance child relations, must use the root parent's RTE to
+	 * fetch the permissions entry because that's the only one that actually
+	 * points to any.
+	 */
+	if (relInfo->ri_RootResultRelInfo)
+		rti = relInfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else
+		rti = relInfo->ri_RangeTableIndex;
+
+	rte = exec_rt_fetch(rti, estate);
+	perminfo = ExecGetRTEPermissionInfo(rte, estate);
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines below
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
 	else
 	{
 		/*
@@ -1286,41 +1360,64 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 		 * firing triggers and the relation is not being inserted into.  (See
 		 * ExecGetTriggerResultRel.)
 		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = ExecGetRTEPermissionInfo(rte, estate);
+	}
+
+	return perminfo;
+}
+
+/* Return a bitmap representing columns being inserted */
+Bitmapset *
+ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
+
+	if (perminfo == NULL)
 		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8f150e9a2e..59b0fdeb62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -507,6 +507,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -560,11 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT64_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 23776367c5..966b75f5a6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -473,6 +473,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -536,11 +537,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ac86ce9003..5013ac3377 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 493a3af0fa..5cddc9d6cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->rtepermlist = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6265,6 +6269,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6392,6 +6397,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..d88fa0b2a8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -78,6 +79,13 @@ typedef struct
 	int			newvarno;
 } fix_windowagg_cond_context;
 
+/* Context info for flatten_rtes_walker() */
+typedef struct
+{
+	PlannerGlobal  *glob;
+	Query		   *query;
+} flatten_rtes_walker_context;
+
 /*
  * Selecting the best alternative in an AlternativeSubPlan expression requires
  * estimating how many times that expression will be evaluated.  For an
@@ -113,8 +121,9 @@ typedef struct
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
-static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
+static bool flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt);
+static void add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
+								   RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 										  IndexOnlyScan *plan,
@@ -355,6 +364,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -375,7 +387,7 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(glob, root->parse->rtepermlist, rte);
 	}
 
 	/*
@@ -442,18 +454,21 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 /*
  * Extract RangeTblEntries from a subquery that was never planned at all
  */
+
 static void
 flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 {
+	flatten_rtes_walker_context cxt = {glob, rte->subquery};
+
 	/* Use query_tree_walker to find all RTEs in the parse tree */
 	(void) query_tree_walker(rte->subquery,
 							 flatten_rtes_walker,
-							 (void *) glob,
+							 (void *) &cxt,
 							 QTW_EXAMINE_RTES_BEFORE);
 }
 
 static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
+flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt)
 {
 	if (node == NULL)
 		return false;
@@ -463,33 +478,39 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(cxt->glob, cxt->query->rtepermlist, rte);
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into subselects */
+		/*
+		 * Recurse into subselects.  Must update cxt->query to this query so
+		 * that the rtable and rtepermlist correspond with each other.
+		 */
+		cxt->query = (Query *) node;
 		return query_tree_walker((Query *) node,
 								 flatten_rtes_walker,
-								 (void *) glob,
+								 (void *) cxt,
 								 QTW_EXAMINE_RTES_BEFORE);
 	}
 	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+								  (void *) cxt);
 }
 
 /*
- * Add (a copy of) the given RTE to the final rangetable
+ * Add (a copy of) the given RTE to the final rangetable and also the
+ * corresponding RTEPermissionInfo, if any, to final rtepermlist.
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo for executor-startup
+ * permission checking.
  */
 static void
-add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
+add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
+					   RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
 
@@ -526,7 +547,23 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 * but it would probably cost more cycles than it would save.
 	 */
 	if (newrte->rtekind == RTE_RELATION)
+	{
+		RTEPermissionInfo *perminfo;
+
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+		/*
+		 * Add the RTEPermissionInfo, if any, corresponding to this RTE to
+		 * the flattened global list.  Also update the perminfoindex to
+		 * reflect the RTEPermissionInfo's new position.
+		 */
+		if (rte->perminfoindex > 0)
+		{
+			perminfo = GetRTEPermissionInfo(rtepermlist, newrte);
+			glob->finalrtepermlist = lappend(glob->finalrtepermlist, perminfo);
+			newrte->perminfoindex = list_length(glob->finalrtepermlist);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..cbeb0191fb 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,6 +1496,14 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
+													  subselect->rtepermlist,
+													  subselect->rtable);
+
 	/* Now we can attach the modified subquery rtable to the parent */
 	parse->rtable = list_concat(parse->rtable, subselect->rtable);
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index f4cdb879c2..8ac0a41d0e 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1205,6 +1198,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
+													  subquery->rtepermlist,
+													  subquery->rtable);
+
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
@@ -1345,6 +1346,13 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
+	/*
+	 * Add subquery's RTEPermissionInfos into the upper query.  This also
+	 * updates the subquery's RTEs' perminfoindex.
+	 */
+	root->parse->rtepermlist = ConcatRTEPermissionInfoLists(root->parse->rtepermlist,
+															subquery->rtepermlist,
+															rtable);
 	/*
 	 * Append child RTEs to parent rtable.
 	 */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..09e2ffdfbc 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -131,6 +133,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +147,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +313,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +333,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +410,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +469,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,9 +492,16 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
+	/*
+	 * No permission checking for the child RTE unless it's the parent
+	 * relation in its child role, which only applies to traditional
+	 * inheritance.
+	 */
+	if (childOID != parentOID)
+		childrte->perminfoindex = 0;
+
 	/* Link not-yet-fully-filled child RTE into data structures */
 	parse->rtable = lappend(parse->rtable, childrte);
 	childRTindex = list_length(parse->rtable);
@@ -539,33 +562,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -866,3 +868,94 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
+
+/*
+ * GetRelAllUpdatedCols
+ * 		Returns the set of columns of a given "simple" relation that are updated
+ * 		by this query
+ */
+Bitmapset *
+GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index	use_relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset *updatedCols,
+			  *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+
+	if (!IS_SIMPLE_REL(rel))
+		return NULL;
+
+	/*
+	 * We need to get the updatedCols bitmapset from the relation's
+	 * RTEPermissionInfo.
+	 *
+	 * If it's a simple "base" rel, can just fetch its RTEPermissionInfo that
+	 * must always be present; this cannot get called on non-RELATION RTEs.
+	 *
+	 * For "other" rels, must look up the root parent relation mentioned in the
+	 * query, because only that one gets assigned a RTEPermissionInfo, and
+	 * translate the columns found therein to match the given relation.
+	 */
+	use_relid = rel->top_parent_relids == NULL ? rel->relid :
+		bms_singleton_member(rel->top_parent_relids);
+	Assert(use_relid == root->parse->resultRelation);
+	rte =  planner_rt_fetch(use_relid, root);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	if (use_relid != rel->relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, use_relid);
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 perminfo->updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  rte->extraUpdatedCols);
+	}
+	else
+	{
+		updatedCols = perminfo->updatedCols;
+		extraUpdatedCols = rte->extraUpdatedCols;
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7b4434e7f..a1dedf52d5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -223,7 +224,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though
+		 * only the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo;
+
+			perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..5279866f43 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,10 +596,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace down
+	 * to the SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to appear
+	 * as part of the SELECT's rtable, not as outer references for it. (Kluge!)
 	 * The SELECT's joinlist is not affected however.  We must do this before
 	 * adding the target table to the INSERT's rtable.
 	 */
@@ -605,6 +607,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 7913523b1c..79171fb725 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -214,6 +214,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -286,7 +287,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -345,7 +346,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -359,8 +360,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4665f0b2b7..fab8083dbb 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1037,11 +1037,15 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo;
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo = GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
-										   col - FirstLowInvalidHeapAttributeNumber);
+		perminfo->selectedCols =
+			bms_add_member(perminfo->selectedCols,
+						   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
@@ -1251,10 +1255,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1290,6 +1297,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1433,6 +1441,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1469,7 +1478,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1478,12 +1487,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1497,7 +1502,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1534,6 +1539,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1557,7 +1563,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1566,12 +1572,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1585,7 +1587,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1659,21 +1661,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1990,20 +1986,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2015,7 +2004,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2082,20 +2071,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2170,19 +2152,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2267,19 +2243,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2294,6 +2264,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2417,19 +2388,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2543,16 +2508,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2564,7 +2526,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3189,6 +3151,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3206,7 +3169,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3855,3 +3821,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index (1-based!) */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	if (rte->perminfoindex == 0 ||
+		rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %d in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = list_nth_node(RTEPermissionInfo, rtepermlist,
+							 rte->perminfoindex - 1);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match provided RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 8e0d6fd01f..56d64c8851 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 487eb2041b..efff8c03b3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1232,7 +1232,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	 * have a failure since both tables are locked.
 	 */
 	attmap = build_attrmap_by_name(RelationGetDescr(childrel),
-								   tupleDesc);
+								   tupleDesc,
+								   false);
 
 	/*
 	 * Process defaults, if required.
@@ -3022,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3080,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3122,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e48a3f589a..568344cc23 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 2ecaa5b907..f2128190d8 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1125,7 +1125,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		/* Map must live as long as the session does. */
 		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 
-		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
+		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
 		MemoryContextSwitchTo(oldctx);
 		RelationClose(ancestor);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index db45d8a08b..3b2649f7a0 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -797,14 +797,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -831,18 +831,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..dde9788755 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *query_rtable;
 
 	context.for_execute = true;
 
@@ -395,32 +396,36 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its permissions list must be preserved so that the
+	 * executor will do the correct permissions checks on the relations
+	 * referenced in it.  This allows us to check that the caller has, say,
+	 * insert-permission on a view, when the view is not semantically
+	 * referenced at all in the resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
+	 * because perminfoindex of those RTEs will be updated there.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	query_rtable = copyObject(parsetree->rtable);
+	sub_action->rtepermlist =
+		ConcatRTEPermissionInfoLists(copyObject(sub_action->rtepermlist),
+									 parsetree->rtepermlist,
+									 query_rtable);
+	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1629,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1658,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1743,6 +1751,8 @@ ApplyRetrieveRule(Query *parsetree,
 	Query	   *rule_action;
 	RangeTblEntry *rte,
 			   *subrte;
+	RTEPermissionInfo *perminfo,
+			   *sub_perminfo;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1793,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1856,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1872,6 +1864,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rte);
 
 	rte->rtekind = RTE_SUBQUERY;
 	rte->subquery = rule_action;
@@ -1881,6 +1874,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;		/* no permission checking for this RTE */
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
@@ -1889,19 +1883,12 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	sub_perminfo = GetRTEPermissionInfo(rule_action->rtepermlist, subrte);
+	sub_perminfo->requiredPerms = perminfo->requiredPerms;
+	sub_perminfo->checkAsUser = perminfo->checkAsUser;
+	sub_perminfo->selectedCols = perminfo->selectedCols;
+	sub_perminfo->insertedCols = perminfo->insertedCols;
+	sub_perminfo->updatedCols = perminfo->updatedCols;
 
 	return parsetree;
 }
@@ -1931,8 +1918,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3064,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3203,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3275,59 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * permissions list so that the executor still performs appropriate
+	 * permissions checks for the query caller's use of the view.
 	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Set new_perminfo->selectedCols to include permission check bits for
+	 * all base-rel columns referenced by the view and insertedCols/updatedCols
+	 * to include all the columns the outer query is trying to modify, adjusting
+	 * the column numbers as needed.  We leave selectedCols as-is, so the view
+	 * owner must have read permission for all columns used in the view
+	 * definition, even if some of them are not read by the outer query.  We
+	 * could try to limit selectedCols to only columns used in the transformed
+	 * query, but that does not correspond to what happens in ordinary SELECT
+	 * usage of a view: all referenced columns must have read permission, even
+	 * if optimization finds that some of them can be discarded during query
+	 * transformation.  The flattening we're doing here is an optional
+	 * optimization, too.  (If you are unpersuaded and want to change this,
+	 * note that applying adjust_view_column_set to view_perminfo->selectedCols
+	 * is clearly *not* the right answer, since that neglects base-rel columns
+	 * used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3431,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3442,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
+		/* Ignore the RTEPermissionInfo that would've been added. */
+		new_exclRte->perminfoindex = 0;
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3717,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3729,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3816,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..8f6446a435 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,33 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * ConcatRTEPermissionInfoLists
+ * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ *
+ * Also updates perminfoindex of the RTEs in src_rtable to point to the
+ * "source" perminfos after they have been added into *dest_rtepermlist.
+ */
+List *
+ConcatRTEPermissionInfoLists(List *dest_rtepermlist, List *src_rtepermlist,
+							 List *src_rtable)
+{
+	ListCell   *l;
+	int			offset = list_length(dest_rtepermlist);
+
+	dest_rtepermlist = list_concat(dest_rtepermlist, src_rtepermlist);
+
+	if (offset > 0)
+	{
+		foreach(l, src_rtable)
+		{
+			RangeTblEntry  *rte = lfirst_node(RangeTblEntry, l);
+
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += offset;
+		}
+	}
+
+	return dest_rtepermlist;
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..baf8c542b8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -32,6 +32,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
@@ -1598,6 +1599,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1648,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc07157037..29f29d664b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index f116924d3c..eabb79db1d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5158,7 +5158,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5210,7 +5210,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5293,7 +5293,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5341,7 +5341,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5402,6 +5402,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5410,7 +5411,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5479,7 +5480,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..787ad8b232 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in case
+		 * it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/access/attmap.h b/src/include/access/attmap.h
index 3ae40cade7..dc0277384f 100644
--- a/src/include/access/attmap.h
+++ b/src/include/access/attmap.h
@@ -42,9 +42,11 @@ extern void free_attrmap(AttrMap *map);
 
 /* Conversion routines to build mappings */
 extern AttrMap *build_attrmap_by_name(TupleDesc indesc,
-									  TupleDesc outdesc);
+									  TupleDesc outdesc,
+									  bool missing_ok);
 extern AttrMap *build_attrmap_by_name_if_req(TupleDesc indesc,
-											 TupleDesc outdesc);
+											 TupleDesc outdesc,
+											 bool missing_ok);
 extern AttrMap *build_attrmap_by_position(TupleDesc indesc,
 										  TupleDesc outdesc,
 										  const char *msg);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..3d2de0ae1b 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..fc98109cdc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+				 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -576,6 +577,7 @@ exec_rt_fetch(Index rti, EState *estate)
 }
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
+extern RTEPermissionInfo *ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 								   Index rti);
 
@@ -600,7 +602,10 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
+extern Oid ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate);
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0f10d4432b..a076cc11a9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
@@ -610,6 +618,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist;		/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f4ed9bbff9..ae73fbe7da 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -152,6 +152,9 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -966,37 +969,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1052,11 +1024,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the containing query's rtepermlist; 0 if permissions
+	 * need not be checked for the RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1176,14 +1153,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns they need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a544b313d3..1ef2f89782 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 5c2ab1b379..e3a5233dd7 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -75,6 +75,10 @@ typedef struct PlannedStmt
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * the rtable entries having
+								 * perminfoindex > 0 */
+
 	List	   *appendRelations;	/* list of AppendRelInfo nodes */
 
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
@@ -703,6 +707,7 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..9a4f86920c 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -23,5 +23,6 @@ extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
+extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..69665aba41 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for
+								 * each RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..76699c8e13 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,8 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern List *ConcatRTEPermissionInfoLists(List *dest_rtepermlist,
+							 List *src_rtepermlist,
+							 List *src_rtable);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
-- 
2.35.3



  [application/octet-stream] v27-0003-ConcatRTEPermissionInfoLists-CombineRangeTable.patch (7.5K, ../../CA+HiwqHQeooqvXy+FSXYOq0wajGtC5-TBj0=ZW1=qVq5Pm2ycw@mail.gmail.com/4-v27-0003-ConcatRTEPermissionInfoLists-CombineRangeTable.patch)
  download | inline diff:
From 50ce1cfbe836b044f03e067e857f6808ba31a086 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Fri, 18 Nov 2022 20:45:06 +0900
Subject: [PATCH v27 3/5] ConcatRTEPermissionInfoLists() -> CombineRangeTable()

---
 src/backend/optimizer/plan/subselect.c    | 13 ++++-------
 src/backend/optimizer/prep/prepjointree.c | 28 ++++++++---------------
 src/backend/rewrite/rewriteHandler.c      | 17 +++++++-------
 src/backend/rewrite/rewriteManip.c        | 26 ++++++++++++---------
 src/include/rewrite/rewriteManip.h        |  6 ++---
 5 files changed, 41 insertions(+), 49 deletions(-)

diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index cbeb0191fb..bc3f7519e4 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1497,15 +1497,12 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 		return NULL;
 
 	/*
-	 * Add subquery's RTEPermissionInfos into the upper query.  This also
-	 * updates the subquery's RTEs' perminfoindex.
+	 * Now we can attach the modified subquery rtable to the parent.
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
-													  subselect->rtepermlist,
-													  subselect->rtable);
-
-	/* Now we can attach the modified subquery rtable to the parent */
-	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	parse->rtable = CombineRangeTables(parse->rtable, subselect->rtable,
+									   subselect->rtepermlist,
+									   &parse->rtepermlist);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 8ac0a41d0e..9733323e3e 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1198,21 +1198,16 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		}
 	}
 
-	/*
-	 * Add subquery's RTEPermissionInfos into the upper query.  This also
-	 * updates the subquery's RTEs' perminfoindex.
-	 */
-	parse->rtepermlist = ConcatRTEPermissionInfoLists(parse->rtepermlist,
-													  subquery->rtepermlist,
-													  subquery->rtable);
-
 	/*
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
 	 * code on the subquery ones too.)
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtable = list_concat(parse->rtable, subquery->rtable);
-
+	parse->rtable = CombineRangeTables(parse->rtable, subquery->rtable,
+									   subquery->rtepermlist,
+									   &parse->rtepermlist);
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
 	 * adjusted the marker rtindexes, so just concat the lists.)
@@ -1346,17 +1341,14 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 		}
 	}
 
-	/*
-	 * Add subquery's RTEPermissionInfos into the upper query.  This also
-	 * updates the subquery's RTEs' perminfoindex.
-	 */
-	root->parse->rtepermlist = ConcatRTEPermissionInfoLists(root->parse->rtepermlist,
-															subquery->rtepermlist,
-															rtable);
 	/*
 	 * Append child RTEs to parent rtable.
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->rtable = CombineRangeTables(root->parse->rtable, rtable,
+											 subquery->rtepermlist,
+											 &root->parse->rtepermlist);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index dde9788755..ac142da7b9 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,7 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
-	List	   *query_rtable;
+	List	   *action_rtepermlist;
 
 	context.for_execute = true;
 
@@ -417,15 +417,14 @@ rewriteRuleAction(Query *parsetree,
 	 * NOTE: because planner will destructively alter rtable and rtepermlist,
 	 * we must ensure that rule action's lists are separate and shares no
 	 * substructure with the main query's lists.  Hence do a deep copy here
-	 * for both.  Copy rtable before calling ConcatRTEPermissionInfoLists(),
-	 * because perminfoindex of those RTEs will be updated there.
+	 * for both.
 	 */
-	query_rtable = copyObject(parsetree->rtable);
-	sub_action->rtepermlist =
-		ConcatRTEPermissionInfoLists(copyObject(sub_action->rtepermlist),
-									 parsetree->rtepermlist,
-									 query_rtable);
-	sub_action->rtable = list_concat(query_rtable, sub_action->rtable);
+	action_rtepermlist = sub_action->rtepermlist;
+	sub_action->rtepermlist = copyObject(parsetree->rtepermlist);
+	sub_action->rtable = CombineRangeTables(copyObject(parsetree->rtable),
+											sub_action->rtable,
+											action_rtepermlist,
+											&sub_action->rtepermlist);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 8f6446a435..1aa7346d9b 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1533,24 +1533,26 @@ ReplaceVarsFromTargetList(Node *node,
 }
 
 /*
- * ConcatRTEPermissionInfoLists
- * 		Add RTEPermissionInfos found in src_rtepermlist into *dest_rtepermlist
+ * CombineRangeTables
+ * 		Adds the RTEs of 'rtable2' into 'rtable1'
  *
- * Also updates perminfoindex of the RTEs in src_rtable to point to the
- * "source" perminfos after they have been added into *dest_rtepermlist.
+ * This also adds the RTEPermissionInfos of 'rtepermlist2' (belonging to the
+ * RTEs in 'rtable2') into *rtepermlist1 and also updates perminfoindex of the
+ * RTEs in 'rtable2' to now point to the perminfos' indexes in *rtepermlist1.
+ *
+ * Note that this changes both 'rtable1' and 'rtepermlist1' destructively, so
+ * the caller should have better passed safe-to-modify copies.
  */
 List *
-ConcatRTEPermissionInfoLists(List *dest_rtepermlist, List *src_rtepermlist,
-							 List *src_rtable)
+CombineRangeTables(List *rtable1, List *rtable2,
+				   List *rtepermlist2, List **rtepermlist1)
 {
 	ListCell   *l;
-	int			offset = list_length(dest_rtepermlist);
-
-	dest_rtepermlist = list_concat(dest_rtepermlist, src_rtepermlist);
+	int			offset = list_length(*rtepermlist1);
 
 	if (offset > 0)
 	{
-		foreach(l, src_rtable)
+		foreach(l, rtable2)
 		{
 			RangeTblEntry  *rte = lfirst_node(RangeTblEntry, l);
 
@@ -1559,5 +1561,7 @@ ConcatRTEPermissionInfoLists(List *dest_rtepermlist, List *src_rtepermlist,
 		}
 	}
 
-	return dest_rtepermlist;
+	*rtepermlist1 = list_concat(*rtepermlist1, rtepermlist2);
+
+	return list_concat(rtable1, rtable2);
 }
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index 76699c8e13..90565a8e17 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,8 +83,8 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
-extern List *ConcatRTEPermissionInfoLists(List *dest_rtepermlist,
-							 List *src_rtepermlist,
-							 List *src_rtable);
+extern List *CombineRangeTables(List *rtable1, List *rtable2,
+				   List *rtepermlist2,
+				   List **rtepermlist1);
 
 #endif							/* REWRITEMANIP_H */
-- 
2.35.3



  [application/octet-stream] v27-0005-Add-per-result-relation-extraUpdatedCols-to-Modi.patch (25.5K, ../../CA+HiwqHQeooqvXy+FSXYOq0wajGtC5-TBj0=ZW1=qVq5Pm2ycw@mail.gmail.com/5-v27-0005-Add-per-result-relation-extraUpdatedCols-to-Modi.patch)
  download | inline diff:
From 99780f5fa818c8e2b8446e4c0f2b7e5e66cda75b Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 4 Oct 2022 20:54:03 +0900
Subject: [PATCH v27 5/5] Add per-result-relation extraUpdatedCols to
 ModifyTable

In spirit of removing things from RangeTblEntry that are better
carried by Query or PlannedStmt directly.
---
 src/backend/executor/execUtils.c         |  9 ++---
 src/backend/executor/nodeModifyTable.c   |  4 ++
 src/backend/nodes/outfuncs.c             |  1 -
 src/backend/nodes/readfuncs.c            |  1 -
 src/backend/optimizer/plan/createplan.c  |  6 ++-
 src/backend/optimizer/plan/planner.c     | 38 +++++++++++++++++++
 src/backend/optimizer/prep/preptlist.c   | 47 ++++++++++++++++++++++++
 src/backend/optimizer/util/inherit.c     | 22 +++++------
 src/backend/optimizer/util/pathnode.c    |  4 ++
 src/backend/replication/logical/worker.c |  6 +--
 src/backend/rewrite/rewriteHandler.c     | 45 -----------------------
 src/include/nodes/execnodes.h            |  3 ++
 src/include/nodes/parsenodes.h           |  1 -
 src/include/nodes/pathnodes.h            |  3 ++
 src/include/nodes/plannodes.h            |  1 +
 src/include/optimizer/inherit.h          |  3 ++
 src/include/optimizer/pathnode.h         |  1 +
 src/include/optimizer/prep.h             |  4 ++
 src/include/rewrite/rewriteHandler.h     |  4 --
 19 files changed, 129 insertions(+), 74 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 51fa9a085e..8a14326854 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1420,20 +1420,17 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
-
-		return rte->extraUpdatedCols;
+		return relinfo->ri_extraUpdatedCols;
 	}
 	else if (relinfo->ri_RootResultRelInfo)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 
 		if (relinfo->ri_RootToPartitionMap != NULL)
 			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+										 rootRelInfo->ri_extraUpdatedCols);
 		else
-			return rte->extraUpdatedCols;
+			return rootRelInfo->ri_extraUpdatedCols;
 	}
 	else
 		return NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b7ea953b55..abf681f401 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3965,6 +3965,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		resultRelInfo = &mtstate->resultRelInfo[i];
 
+		if (node->extraUpdatedColsBitmaps)
+			resultRelInfo->ri_extraUpdatedCols =
+				list_nth(node->extraUpdatedColsBitmaps, i);
+
 		/* Let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 59b0fdeb62..2d369e0340 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -561,7 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 966b75f5a6..d720aea857 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -537,7 +537,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5013ac3377..2360ae280b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -308,7 +308,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
 									 List *resultRelations,
-									 List *updateColnosLists,
+									 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *rowMarks, OnConflictExpr *onconflict,
 									 List *mergeActionLists, int epqParam);
@@ -2824,6 +2824,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partColsUpdated,
 							best_path->resultRelations,
 							best_path->updateColnosLists,
+							best_path->extraUpdatedColsBitmaps,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
@@ -6980,7 +6981,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
 				 List *resultRelations,
-				 List *updateColnosLists,
+				 List *updateColnosLists, List *extraUpdatedColsBitmaps,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, OnConflictExpr *onconflict,
 				 List *mergeActionLists, int epqParam)
@@ -7049,6 +7050,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
 	node->updateColnosLists = updateColnosLists;
+	node->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5cddc9d6cf..4178eb416d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1746,6 +1746,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			Index		rootRelation;
 			List	   *resultRelations = NIL;
 			List	   *updateColnosLists = NIL;
+			List	   *extraUpdatedColsBitmaps = NIL;
 			List	   *withCheckOptionLists = NIL;
 			List	   *returningLists = NIL;
 			List	   *mergeActionLists = NIL;
@@ -1779,15 +1780,35 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					if (parse->commandType == CMD_UPDATE)
 					{
 						List	   *update_colnos = root->update_colnos;
+						Bitmapset  *extraUpdatedCols = root->extraUpdatedCols;
 
 						if (this_result_rel != top_result_rel)
+						{
 							update_colnos =
 								adjust_inherited_attnums_multilevel(root,
 																	update_colnos,
 																	this_result_rel->relid,
 																	top_result_rel->relid);
+							extraUpdatedCols =
+								translate_col_privs_multilevel(root, this_result_rel,
+															   top_result_rel,
+															   extraUpdatedCols);
+						}
 						updateColnosLists = lappend(updateColnosLists,
 													update_colnos);
+						/*
+						 * Make extraUpdatedCols bitmap look as a proper Node
+						 * before adding into the List so that Node
+						 * copy/write/read handle it correctly.
+						 *
+						 * XXX should be using makeNode(Bitmapset) somewhere?
+						 */
+						if (extraUpdatedCols)
+						{
+							extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = lappend(extraUpdatedColsBitmaps,
+															  extraUpdatedCols);
+						}
 					}
 					if (parse->withCheckOptions)
 					{
@@ -1869,7 +1890,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 					 */
 					resultRelations = list_make1_int(parse->resultRelation);
 					if (parse->commandType == CMD_UPDATE)
+					{
 						updateColnosLists = list_make1(root->update_colnos);
+						/* See the comment in the inherited UPDATE block. */
+						if (root->extraUpdatedCols)
+						{
+							root->extraUpdatedCols->type = T_Bitmapset;
+							extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+						}
+					}
 					if (parse->withCheckOptions)
 						withCheckOptionLists = list_make1(parse->withCheckOptions);
 					if (parse->returningList)
@@ -1883,7 +1912,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				/* Single-relation INSERT/UPDATE/DELETE/MERGE. */
 				resultRelations = list_make1_int(parse->resultRelation);
 				if (parse->commandType == CMD_UPDATE)
+				{
 					updateColnosLists = list_make1(root->update_colnos);
+					/* See the comment in the inherited UPDATE block. */
+					if (root->extraUpdatedCols)
+					{
+						root->extraUpdatedCols->type = T_Bitmapset;
+						extraUpdatedColsBitmaps = list_make1(root->extraUpdatedCols);
+					}
+				}
 				if (parse->withCheckOptions)
 					withCheckOptionLists = list_make1(parse->withCheckOptions);
 				if (parse->returningList)
@@ -1922,6 +1959,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 										root->partColsUpdated,
 										resultRelations,
 										updateColnosLists,
+										extraUpdatedColsBitmaps,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 137b28323d..6bbcb6ac1d 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -43,6 +43,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "utils/rel.h"
 
@@ -106,6 +107,17 @@ preprocess_targetlist(PlannerInfo *root)
 	else if (command_type == CMD_UPDATE)
 		root->update_colnos = extract_update_targetlist_colnos(tlist);
 
+	/* Also populate extraUpdatedCols (for generated columns) */
+	if (command_type == CMD_UPDATE)
+	{
+		RTEPermissionInfo *target_perminfo =
+			GetRTEPermissionInfo(parse->rtepermlist, target_rte);
+
+		root->extraUpdatedCols =
+			get_extraUpdatedCols(target_perminfo->updatedCols,
+								 target_relation);
+	}
+
 	/*
 	 * For non-inherited UPDATE/DELETE/MERGE, register any junk column(s)
 	 * needed to allow the executor to identify the rows to be updated or
@@ -337,6 +349,41 @@ extract_update_targetlist_colnos(List *tlist)
 	return update_colnos;
 }
 
+/*
+ * Return the indexes of any generated columns that depend on any columns
+ * mentioned in updatedCols.
+ */
+Bitmapset *
+get_extraUpdatedCols(Bitmapset *updatedCols, Relation target_relation)
+{
+	TupleDesc	tupdesc = RelationGetDescr(target_relation);
+	TupleConstr *constr = tupdesc->constr;
+	Bitmapset *extraUpdatedCols = NULL;
+
+	if (constr && constr->has_generated_stored)
+	{
+		for (int i = 0; i < constr->num_defval; i++)
+		{
+			AttrDefault *defval = &constr->defval[i];
+			Node	   *expr;
+			Bitmapset  *attrs_used = NULL;
+
+			/* skip if not generated column */
+			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
+				continue;
+
+			/* identify columns this generated column depends on */
+			expr = stringToNode(defval->adbin);
+			pull_varattnos(expr, 1, &attrs_used);
+
+			if (bms_overlap(updatedCols, attrs_used))
+				extraUpdatedCols = bms_add_member(extraUpdatedCols,
+												  defval->adnum - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
+	return extraUpdatedCols;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 09e2ffdfbc..ecfa179bfc 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -40,6 +40,7 @@ static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
 									   Bitmapset *parent_updatedCols,
+									   Bitmapset *parent_extraUpdatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -149,6 +150,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		expand_partitioned_rtentry(root, rel, rte, rti,
 								   oldrelation,
 								   perminfo->updatedCols,
+								   root->extraUpdatedCols,
 								   oldrc, lockmode);
 	}
 	else
@@ -314,6 +316,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
 						   Bitmapset *parent_updatedCols,
+						   Bitmapset *parent_extraUpdatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -344,7 +347,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	/*
 	 * There shouldn't be any generated columns in the partition key.
 	 */
-	Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
+	Assert(!has_partition_attrs(parentrel, parent_extraUpdatedCols, NULL));
 
 	/* Nothing further to do here if there are no partitions. */
 	if (partdesc->nparts == 0)
@@ -413,14 +416,18 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		{
 			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
 			Bitmapset *child_updatedCols;
+			Bitmapset *child_extraUpdatedCols;
 
 			child_updatedCols = translate_col_privs(parent_updatedCols,
 													appinfo->translated_vars);
+			child_extraUpdatedCols = translate_col_privs(parent_extraUpdatedCols,
+														 appinfo->translated_vars);
 
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel,
 									   child_updatedCols,
+									   child_extraUpdatedCols,
 									   top_parentrc, lockmode);
 		}
 
@@ -562,13 +569,6 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/* Translate the bitmapset of generated columns being updated. */
-	if (childOID != parentOID)
-		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
-														 appinfo->translated_vars);
-	else
-		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
 	 * the caller must already have allocated space for.
@@ -875,7 +875,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
  * 		'top_parent_cols' to the columns numbers of a descendent relation
  * 		given by 'relid'
  */
-static Bitmapset *
+Bitmapset *
 translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
 							   RelOptInfo *top_parent_rel,
 							   Bitmapset *top_parent_cols)
@@ -949,12 +949,12 @@ GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel)
 		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
 													 perminfo->updatedCols);
 		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
-														  rte->extraUpdatedCols);
+														  root->extraUpdatedCols);
 	}
 	else
 	{
 		updatedCols = perminfo->updatedCols;
-		extraUpdatedCols = rte->extraUpdatedCols;
+		extraUpdatedCols = root->extraUpdatedCols;
 	}
 
 	return bms_union(updatedCols, extraUpdatedCols);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 6dd11329fb..5b489da57d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3644,6 +3644,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'updateColnosLists' is a list of UPDATE target column number lists
  *		(one sublist per rel); or NIL if not an UPDATE
+ * 'extraUpdatedColsBitmaps' is a list of generated column attribute number
+ *		bitmapsets (one bitmapset per rel); or NIL if not an UPDATE
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3659,6 +3661,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						bool partColsUpdated,
 						List *resultRelations,
 						List *updateColnosLists,
+						List *extraUpdatedColsBitmaps,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						List *mergeActionLists, int epqParam)
@@ -3723,6 +3726,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
 	pathnode->updateColnosLists = updateColnosLists;
+	pathnode->extraUpdatedColsBitmaps = extraUpdatedColsBitmaps;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 568344cc23..eb18a04908 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/prep.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -1815,7 +1816,6 @@ apply_handle_update(StringInfo s)
 	LogicalRepTupleData newtup;
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
-	RangeTblEntry *target_rte;
 	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
@@ -1864,7 +1864,6 @@ apply_handle_update(StringInfo s)
 	 * information.  But it would for example exclude columns that only exist
 	 * on the subscriber, since we are not touching those.
 	 */
-	target_rte = list_nth(estate->es_range_table, 0);
 	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
@@ -1882,7 +1881,8 @@ apply_handle_update(StringInfo s)
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
+	edata->targetRelInfo->ri_extraUpdatedCols =
+		get_extraUpdatedCols(target_perminfo->updatedCols, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 45d03bacbe..5261add7b2 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1626,46 +1626,6 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 }
 
 
-/*
- * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * columns that depend on any columns mentioned in
- * target_perminfo->updatedCols.
- */
-void
-fill_extraUpdatedCols(RangeTblEntry *target_rte,
-					  RTEPermissionInfo *target_perminfo,
-					  Relation target_relation)
-{
-	TupleDesc	tupdesc = RelationGetDescr(target_relation);
-	TupleConstr *constr = tupdesc->constr;
-
-	target_rte->extraUpdatedCols = NULL;
-
-	if (constr && constr->has_generated_stored)
-	{
-		for (int i = 0; i < constr->num_defval; i++)
-		{
-			AttrDefault *defval = &constr->defval[i];
-			Node	   *expr;
-			Bitmapset  *attrs_used = NULL;
-
-			/* skip if not generated column */
-			if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
-				continue;
-
-			/* identify columns this generated column depends on */
-			expr = stringToNode(defval->adbin);
-			pull_varattnos(expr, 1, &attrs_used);
-
-			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
-				target_rte->extraUpdatedCols =
-					bms_add_member(target_rte->extraUpdatedCols,
-								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
-		}
-	}
-}
-
-
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3716,7 +3676,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
-		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3728,7 +3687,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
-		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3813,9 +3771,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									parsetree->override,
 									rt_entry_relation,
 									NULL, 0, NULL);
-
-			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a076cc11a9..063932e4d2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -471,6 +471,9 @@ typedef struct ResultRelInfo
 	/* Have the projection and the slots above been initialized? */
 	bool		ri_projectNewInfoValid;
 
+	/* generated column attribute numbers */
+	Bitmapset   *ri_extraUpdatedCols;
+
 	/* triggers to be fired, if any */
 	TriggerDesc *ri_TrigDesc;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ae73fbe7da..f81b74a062 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1153,7 +1153,6 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
 	List	   *securityQuals;	/* security barrier quals to apply, if any */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1ef2f89782..23e1cfc2fb 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -422,6 +422,8 @@ struct PlannerInfo
 	 */
 	List	   *update_colnos;
 
+	Bitmapset  *extraUpdatedCols;
+
 	/*
 	 * Fields filled during create_plan() for use in setrefs.c
 	 */
@@ -2248,6 +2250,7 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e3a5233dd7..2e7b9a289d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -237,6 +237,7 @@ typedef struct ModifyTable
 	bool		partColsUpdated;	/* some part key in hierarchy updated? */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *updateColnosLists;	/* per-target-table update_colnos lists */
+	List	   *extraUpdatedColsBitmaps; /* per-target-table extraUpdatedCols bitmaps */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index 9a4f86920c..a729401031 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -24,5 +24,8 @@ extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
 extern Bitmapset *GetRelAllUpdatedCols(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols);
 
 #endif							/* INHERIT_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 050f00e79a..fd16d94916 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -278,6 +278,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												bool partColsUpdated,
 												List *resultRelations,
 												List *updateColnosLists,
+												List *extraUpdatedColsBitmaps,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												List *mergeActionLists, int epqParam);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 5b4f350b33..92753c9670 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -39,6 +40,9 @@ extern void preprocess_targetlist(PlannerInfo *root);
 
 extern List *extract_update_targetlist_colnos(List *tlist);
 
+extern Bitmapset *get_extraUpdatedCols(Bitmapset *updatedCols,
+									   Relation target_relation);
+
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 05c3680cd6..b4f96f298b 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -24,10 +24,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 
 extern Node *build_column_default(Relation rel, int attrno);
 
-extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
-								  RTEPermissionInfo *target_perminfo,
-								  Relation target_relation);
-
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 												bool check_cols);
-- 
2.35.3



  [application/octet-stream] v27-0004-Do-not-add-the-NEW-entry-to-view-rule-action-s-r.patch (10.3K, ../../CA+HiwqHQeooqvXy+FSXYOq0wajGtC5-TBj0=ZW1=qVq5Pm2ycw@mail.gmail.com/6-v27-0004-Do-not-add-the-NEW-entry-to-view-rule-action-s-r.patch)
  download | inline diff:
From 220cc278ab4fc7757231a591dcb505be43b93857 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Mon, 21 Nov 2022 15:27:56 +0900
Subject: [PATCH v27 4/5] Do not add the NEW entry to view rule action's range
 table

The OLD entry suffices as a placeholder for the view relation when
it is queried, such as checking its permissions during a query's
execution, but the NEW entry has no role to play whatsoever.

Because there are now fewer entries in the view query's range table,
this change affects how the deparsed queries look, where the output
of deparsing (such as alias names) depends on using RT indexs and
the original query references a view.  To wit, some postgres_fdw
regression tests whose expected output changes as a result of this
have been are updated to match.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  4 +-
 src/backend/commands/lockcmds.c               |  6 +-
 src/backend/commands/view.c                   | 74 +++++++------------
 src/backend/rewrite/rewriteDefine.c           |  6 +-
 src/backend/rewrite/rewriteHandler.c          |  4 +-
 5 files changed, 35 insertions(+), 59 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 558e94b845..a84176cf65 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2606,7 +2606,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1
  Foreign Scan
    Output: ft4.c1, ft5.c2, ft5.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5)
-   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r7.c2, r7.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r7 ON (((r5.c1 = r7.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r7.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
@@ -2669,7 +2669,7 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
  Foreign Scan
    Output: ft4.c1, t2.c2, t2.c1
    Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
-   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
+   Remote SQL: SELECT r5.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r2 ON (((r5.c1 = r2.c1)))) ORDER BY r5.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b0747ce291..ce0e6ac112 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -195,12 +195,10 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 			char	   *relname = get_rel_name(relid);
 
 			/*
-			 * The OLD and NEW placeholder entries in the view's rtable are
-			 * skipped.
+			 * The OLD placeholder entry in the view's rtable is skipped.
 			 */
 			if (relid == context->viewoid &&
-				(strcmp(rte->eref->aliasname, "old") == 0 ||
-				 strcmp(rte->eref->aliasname, "new") == 0))
+				(strcmp(rte->eref->aliasname, "old") == 0))
 				continue;
 
 			/* Currently, we only allow plain tables or views to be locked. */
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6bb707fd51..347c797b58 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -356,29 +356,25 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 /*---------------------------------------------------------------
  * UpdateRangeTableOfViewParse
  *
- * Update the range table of the given parsetree.
- * This update consists of adding two new entries IN THE BEGINNING
- * of the range table (otherwise the rule system will die a slow,
- * horrible and painful death, and we do not want that now, do we?)
- * one for the OLD relation and one for the NEW one (both of
- * them refer in fact to the "view" relation).
+ * Update the range table of the given parsetree to add a placeholder entry
+ * for the view relation and increase the 'varnos' of all the Var nodes
+ * by 1 to account for its addition.
  *
- * Of course we must also increase the 'varnos' of all the Var nodes
- * by 2...
- *
- * These extra RT entries are not actually used in the query,
- * except for run-time locking.
+ * This extra RT entry for the view relation is not actually used in the query
+ * but it is needed so that 1) the executor can checks the permissions via the
+ * RTEPermissionInfo that is also added in this function, 2) the executor can
+ * lock the view, and 3) the planner can record the view's OID in
+ * PlannedStmt.relationOids such that any concurrent changes to its schema
+ * would invlidate the plans refencing the view.
  *---------------------------------------------------------------
  */
 static Query *
 UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 {
 	Relation	viewRel;
-	List	   *new_rt;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rt_entry1,
-			   *rt_entry2;
-	RTEPermissionInfo *rte_perminfo1;
+	RangeTblEntry *rt_entry;
+	RTEPermissionInfo *rte_perminfo;
 	ParseState *pstate;
 	ListCell   *lc;
 
@@ -399,31 +395,25 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	viewRel = relation_open(viewOid, AccessShareLock);
 
 	/*
-	 * Create the 2 new range table entries and form the new range table...
-	 * OLD first, then NEW....
+	 * Create the new range table entry and form the new range table where
+	 * the OLD entry is added first, followed by the entries in the view
+	 * query's range table.
 	 */
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("old", NIL),
 										   false, false);
-	rt_entry1 = nsitem->p_rte;
-	rte_perminfo1 = nsitem->p_perminfo;
-	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
-	rt_entry2 = nsitem->p_rte;
+	rt_entry = nsitem->p_rte;
+	rte_perminfo = nsitem->p_perminfo;
 
 	/*
-	 * Add only the "old" RTEPermissionInfo at the head of view query's list
-	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
-	 * query on the view, ApplyRetrieveRule() will transfer the view relation's
-	 * permission details into this RTEPermissionInfo.  That's needed because
-	 * the view's RTE itself will be transposed into a subquery RTE that can't
-	 * carry the permission details; see the code stanza toward the end of
-	 * ApplyRetrieveRule() for how that's done.
+	 * When rewriting a query on the view, ApplyRetrieveRule() will transfer
+	 * the view relation's permission details into this RTEPermissionInfo.
+	 * That's needed because the view's RTE itself will be transposed into a
+	 * subquery RTE that can't carry the permission details; see the code
+	 * stanza toward the end of ApplyRetrieveRule() for how that's done.
 	 */
-	viewParse->rtepermlist = lcons(rte_perminfo1, viewParse->rtepermlist);
+	viewParse->rtepermlist = lcons(rte_perminfo, viewParse->rtepermlist);
 	foreach(lc, viewParse->rtable)
 	{
 		RangeTblEntry *rte = lfirst(lc);
@@ -431,22 +421,12 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 		if (rte->perminfoindex > 0)
 			rte->perminfoindex += 1;
 	}
-	/*
-	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is bit
-	 * of a hack given that all the non-child RTE_RELATION entries really
-	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
-	 * go away anyway in the very near future.
-	 */
-	rt_entry2->perminfoindex = 0;
-
-	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
-
-	viewParse->rtable = new_rt;
+	viewParse->rtable = lcons(rt_entry, viewParse->rtable);
 
 	/*
-	 * Now offset all var nodes by 2, and jointree RT indexes too.
+	 * Now offset all var nodes by 1, and jointree RT indexes too.
 	 */
-	OffsetVarNodes((Node *) viewParse, 2, 0);
+	OffsetVarNodes((Node *) viewParse, 1, 0);
 
 	relation_close(viewRel, AccessShareLock);
 
@@ -616,8 +596,8 @@ void
 StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
 {
 	/*
-	 * The range table of 'viewParse' does not contain entries for the "OLD"
-	 * and "NEW" relations. So... add them!
+	 * Add a placeholder entry for the "OLD" relation to the range table of
+	 * 'viewParse'; see the header comment for why it's needed.
 	 */
 	viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 3b2649f7a0..dd31bc90ae 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -801,10 +801,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
  * RTE entry's RTEPermissionInfo will be overridden when the view rule is
- * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
- * irrelevant because its requiredPerms bits will always be zero.  However, for
- * other types of rules it's important to set these fields to match the rule
- * owner.  So we just set them always.
+ * expanded.  However, for other types of rules it's important to set these
+ * fields to match the rule owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index ac142da7b9..45d03bacbe 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1900,8 +1900,8 @@ ApplyRetrieveRule(Query *parsetree,
  *
  * NB: this must agree with the parser's transformLockingClause() routine.
  * However, unlike the parser we have to be careful not to mark a view's
- * OLD and NEW rels for updating.  The best way to handle that seems to be
- * to scan the jointree to determine which rels are used.
+ * OLD rel for updating.  The best way to handle that seems to be to scan
+ * the jointree to determine which rels are used.
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-29 09:27  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2022-11-29 09:27 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Thanks for the new version, in particular thank you for fixing the
annoyance with the CombineRangeTables API.

0002 was already pushed upstream, so we can forget about it.  I also
pushed the addition of missing_ok to build_attrmap_by_name{,_if_req}.
So this series needed a refresh, which is attached here, and tests are
running: https://cirrus-ci.com/build/4880219807416320


As for 0001+0003, here it is once again with a few fixups.  There are
two nontrivial changes here:

1. in get_rel_all_updated_cols (née GetRelAllUpdatedCols, which I
changed because it didn't match the naming style in inherits.c) you were
doing determining the relid to use in a roundabout way, then asserting
it is a value you already know:

-   use_relid = rel->top_parent_relids == NULL ? rel->relid :
-       bms_singleton_member(rel->top_parent_relids);
-   Assert(use_relid == root->parse->resultRelation);

Why not just use root->parse->resultRelation in the first place?
My 0002 does that.

2. my 0005 moves a block in add_rte_to_flat_rtable one level out:
there's no need to put it inside the rtekind == RTE_RELATION block, and
the comment in that block failed to mention that we copied the
RTEPermissionInfo; we can just let it work on the 'perminfoindex > 0'
condition.  Also, the comment is a bit misleading, and I changed it
some, but maybe not sufficiently: after add_rte_to_flat_rtable, the same
RTEPermissionInfo node will serve two RTEs: one in the Query struct,
whose perminfoindex corresponds to Query->rtepermlist; and the other in
PlannerGlobal->finalrtable, whose index corresponds to
PlannerGlobal->finalrtepermlist.  I was initially thinking that the old
RTE would result in a "corrupted" state, but that doesn't appear to be
the case.  (Also: I made it grab the RTEPermissionInfo using
rte->perminfoindex, not newrte->perminfoindex, because that seems
slightly bogus, even if they are identical because of the memcpy.)

The other changes are cosmetic.

I do not include here your 0004 and 0005.  (I think we can deal with
those separately later.)

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-29 13:37  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 4 replies; 73+ messages in thread

From: Amit Langote @ 2022-11-29 13:37 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi Alvaro,

Thanks for taking a look and all the fixup patches.  Was working on
that test I said we should add and then was spending some time
cleaning things up and breaking some things out into their patches,
mainly for the ease of review.

On Tue, Nov 29, 2022 at 6:27 PM Alvaro Herrera <[email protected]> wrote:
> Thanks for the new version, in particular thank you for fixing the
> annoyance with the CombineRangeTables API.

OK, now that you seem to think that looks good, I've merged it into
the main patch.

> 0002 was already pushed upstream, so we can forget about it.  I also
> pushed the addition of missing_ok to build_attrmap_by_name{,_if_req}.

Yeah, I thought that needed to be broken out and had done so in my
local repo.  Thanks for pushing that bit.

> As for 0001+0003, here it is once again with a few fixups.  There are
> two nontrivial changes here:
>
> 1. in get_rel_all_updated_cols (née GetRelAllUpdatedCols, which I
> changed because it didn't match the naming style in inherits.c) you were
> doing determining the relid to use in a roundabout way, then asserting
> it is a value you already know:
>
> -   use_relid = rel->top_parent_relids == NULL ? rel->relid :
> -       bms_singleton_member(rel->top_parent_relids);
> -   Assert(use_relid == root->parse->resultRelation);

> Why not just use root->parse->resultRelation in the first place?

Facepalm, yes.

> My 0002 does that.

Merged.

> 2. my 0005 moves a block in add_rte_to_flat_rtable one level out:
> there's no need to put it inside the rtekind == RTE_RELATION block, and
> the comment in that block failed to mention that we copied the
> RTEPermissionInfo; we can just let it work on the 'perminfoindex > 0'
> condition.

Yes, agree that's better.

>  Also, the comment is a bit misleading, and I changed it
> some, but maybe not sufficiently: after add_rte_to_flat_rtable, the same
> RTEPermissionInfo node will serve two RTEs: one in the Query struct,
> whose perminfoindex corresponds to Query->rtepermlist; and the other in
> PlannerGlobal->finalrtable, whose index corresponds to
> PlannerGlobal->finalrtepermlist.  I was initially thinking that the old
> RTE would result in a "corrupted" state, but that doesn't appear to be
> the case.  (Also: I made it grab the RTEPermissionInfo using
> rte->perminfoindex, not newrte->perminfoindex, because that seems
> slightly bogus, even if they are identical because of the memcpy.)

Interesting point about two different RTEs (in different lists)
pointing to the same RTEPermissionInfo, also in different lists.
Maybe, we should have the following there so that the PlannedStmt's
contents don't point into the Query?

    newperminfo = copyObject(perminfo);

> The other changes are cosmetic.

Thanks, I've merged all.  I do wonder that it is only in PlannedStmt
that the list is called something that is not "rtepermlist", but I'm
fine with it if you prefer that.

> I do not include here your 0004 and 0005.  (I think we can deal with
> those separately later.)

OK, I have not attached them with this email either.

As I mentioned above, I've broken a couple of other changes out into
their own patches that I've put before the main patch.  0001 adds
ExecGetRootToChildMap().  I thought it would be better to write in the
commit message why the new map is necessary for the main patch.   0002
contains changes that has to do with changing how we access
checkAsUser in some foreign table planning/execution code sites.
Thought it might be better to describe it separately too.

0003 is the main patch into which I've merged both my patch that
invents CombineRangeTables() that I had posted separately before and
all of your fixups.  In it, you will see a new test case that I have
added in rules.sql to exercise the permission checking order stuff
that I had said I may have broken with this patch, especially the
hunks that change rewriteRuleAction().  That test would be broken with
v24, but not after the changes to add_rtes_to_flat_rtable() that I
made to address your review comment that blindly list_concat'ing
finalrtepermlist and Query's rtepermlist doesn't look very robust,
which it indeed wasn't [1].

--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

[1] So, rewriteRuleAction(), with previous "wrong" versions of the
patch (~v26), would combine the original query's and action query's
rtepermlists in the "wrong" order, that is, not in the order in which
RTEs appear in the combined rtable.   But because
add_rtes_to_flat_rtable() now (v26~) adds perminfos into
finalrtepermlist in the RTE order using lappend(), that wrongness of
rewriteRuleAction() would be masked -- no execution-time failure of
the test.  Anyway, I've also fixed rewriteRuleAction() to be "correct"
in v27, so it is the least wrong version AFAIK ;).


Attachments:

  [application/octet-stream] v29-0003-Rework-query-relation-permission-checking.patch (132.6K, ../../CA+HiwqGFCs2uq7VRKi7g+FFKbP6Ea_2_HkgZb2HPhUfaAKT3ng@mail.gmail.com/2-v29-0003-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 6a7366c4a043a6a9d0fb2dfae42ded7f14ec5061 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v29 3/5] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  17 +-
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/commands/copy.c                   |  23 ++-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/view.c                   |  33 ++-
 src/backend/executor/execMain.c               | 122 ++++++-----
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execUtils.c              | 142 ++++++++-----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  64 ++++--
 src/backend/optimizer/plan/subselect.c        |   9 +-
 src/backend/optimizer/prep/prepjointree.c     |  19 +-
 src/backend/optimizer/util/inherit.c          | 173 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  68 ++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 178 +++++++++-------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   6 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 193 +++++++++---------
 src/backend/rewrite/rewriteManip.c            |  34 +++
 src/backend/rewrite/rowsecurity.c             |  24 ++-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  13 +-
 src/include/nodes/execnodes.h                 |   3 +-
 src/include/nodes/parsenodes.h                |  94 ++++++---
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   3 +
 src/include/optimizer/inherit.h               |   2 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   3 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 src/test/regress/expected/rules.out           |  14 ++
 src/test/regress/sql/rules.sql                |  15 ++
 src/tools/pgindent/typedefs.list              |   2 +
 46 files changed, 954 insertions(+), 537 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index afe234ea66..a87a280d99 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -657,8 +658,8 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to lack
+	 * of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
@@ -1808,7 +1809,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = get_rel_all_updated_cols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -2649,7 +2651,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
 	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
@@ -3974,11 +3976,8 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	/* Identify which user to do the remote access as. */
+	userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..3509358fbe 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rtepermlist,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rtepermlist)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..6e7f96e7db 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rtepermlist, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rtepermlist, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rtepermlist, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..7aa6df92ec 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rtepermlist,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..b8bd78d358 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -150,15 +151,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
 		foreach(cur, attnums)
 		{
-			int			attno = lfirst_int(cur) -
-			FirstLowInvalidHeapAttributeNumber;
+			int			attno;
+			Bitmapset **bms;
 
-			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
-			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+			attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
+			bms = is_from ? &perminfo->insertedCols : &perminfo->selectedCols;
+
+			*bms = bms_add_member(*bms, attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +175,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a079c70152..03f8ec459a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rtepermlist = cstate->rtepermlist;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rtepermlist, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rtepermlist = pstate->p_rtepermlist;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..cdd73e148e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
+	RTEPermissionInfo *rte_perminfo1;
 	ParseState *pstate;
+	ListCell   *lc;
 
 	/*
 	 * Make a copy of the given parsetree.  It's not so much that we don't
@@ -405,15 +407,38 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   makeAlias("old", NIL),
 										   false, false);
 	rt_entry1 = nsitem->p_rte;
+	rte_perminfo1 = nsitem->p_perminfo;
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("new", NIL),
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
+	/*
+	 * Add only the "old" RTEPermissionInfo at the head of view query's list
+	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
+	 * query on the view, ApplyRetrieveRule() will transfer the view
+	 * relation's permission details into this RTEPermissionInfo.  That's
+	 * needed because the view's RTE itself will be transposed into a subquery
+	 * RTE that can't carry the permission details; see the code stanza toward
+	 * the end of ApplyRetrieveRule() for how that's done.
+	 */
+	viewParse->rtepermlist = lcons(rte_perminfo1, viewParse->rtepermlist);
+	foreach(lc, viewParse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += 1;
+	}
+
+	/*
+	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is a
+	 * bit of a hack given that all the non-child RTE_RELATION entries really
+	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
+	 * go away anyway in the very near future.
+	 */
+	rt_entry2->perminfoindex = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index e301c687e3..037b92702b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -54,6 +54,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -74,8 +75,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -90,10 +91,10 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
-									  Bitmapset *modifiedCols,
-									  AclMode requiredPerms);
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
+										 Bitmapset *modifiedCols,
+										 AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
 static char *ExecBuildSlotValueDescription(Oid reloid,
 										   TupleTableSlot *slot,
@@ -554,8 +555,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -565,73 +566,64 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rtepermlist,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rtepermlist)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV,
+							   get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rtepermlist,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down
+	 * from there.  But for now, no need for the extra clutter.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -665,14 +657,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -697,29 +689,31 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->insertedCols,
-																	  ACL_INSERT))
+		if (remainingPerms & ACL_INSERT &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->insertedCols,
+										  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->updatedCols,
-																	  ACL_UPDATE))
+		if (remainingPerms & ACL_UPDATE &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->updatedCols,
+										  ACL_UPDATE))
 			return false;
 	}
 	return true;
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
-						  AclMode requiredPerms)
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+							 AclMode requiredPerms)
 {
 	int			col = -1;
 
@@ -773,17 +767,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->permInfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -815,9 +806,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->permInfos, true);
+	estate->es_rtepermlist = plannedstmt->permInfos;
 
 	/*
 	 * initialize the node's execution state
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 99512826c5..2ee84f7612 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -184,6 +184,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->parallelModeNeeded = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->permInfos = estate->es_rtepermlist;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index e2b4272d90..8e9375aa6a 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -67,6 +68,7 @@
 
 static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, int varno, TupleDesc tupdesc);
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
+static inline RTEPermissionInfo *GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate);
 
 
 /* ----------------------------------------------------------------
@@ -1295,72 +1297,48 @@ ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
-	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+	if (perminfo == NULL)
+		return NULL;
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
-		else
-			return rte->insertedCols;
-	}
-	else
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		/*
-		 * The relation isn't in the range table and it isn't a partition
-		 * routing target.  This ResultRelInfo must've been created only for
-		 * firing triggers and the relation is not being inserted into.  (See
-		 * ExecGetTriggerResultRel.)
-		 */
-		return NULL;
+		AttrMap    *map = ExecGetRootToChildMap(relinfo, estate);
+
+		if (map)
+			return execute_attr_map_cols(map, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		AttrMap    *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
@@ -1389,3 +1367,75 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 	return bms_union(ExecGetUpdatedCols(relinfo, estate),
 					 ExecGetExtraUpdatedCols(relinfo, estate));
 }
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Helper routine for ExecGet*Cols() routines
+ *
+ * The column bitmapsets are stored in RTEPermissionInfos.  For inheritance
+ * child result relations (a partition routing target of an INSERT or a child
+ * UPDATE target), use the root parent's RTE to fetch the RTEPermissionInfo
+ * because that's the only one that actually points to any.
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	else if (relinfo->ri_RangeTableIndex != 0)
+		rti = relinfo->ri_RangeTableIndex;
+	else
+	{
+		/*
+		 * The relation isn't in the range table and it isn't a partition
+		 * routing target.  This ResultRelInfo must've been created only for
+		 * firing triggers and the relation is not being inserted into.  (See
+		 * ExecGetTriggerResultRel.)
+		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = ExecGetRTEPermissionInfo(rte, estate);
+	}
+
+	return perminfo;
+}
+
+/*
+ * ExecGetRTEPermissionInfo
+ *		Returns the RTEPermissionInfo contained in estate->es_rtepermlist
+ *		pointed to by the RTE
+ */
+RTEPermissionInfo *
+ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate)
+{
+	Assert(estate->es_rtepermlist != NIL);
+	return GetRTEPermissionInfo(estate->es_rtepermlist, rte);
+}
+
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+Oid
+ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relInfo, estate);
+
+	/* XXX - maybe ok to return GetUserId() in this case? */
+	if (perminfo == NULL)
+		elog(ERROR, "no RTEPermissionInfo found for result relation with OID %u",
+			 RelationGetRelid(relInfo->ri_RelationDesc));
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8f150e9a2e..59b0fdeb62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -507,6 +507,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -560,11 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT64_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 23776367c5..966b75f5a6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -473,6 +473,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -536,11 +537,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 493a3af0fa..0bf772d4ee 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrtepermlist = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrtepermlist == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -520,6 +523,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->parallelModeNeeded = glob->parallelModeNeeded;
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
+	result->permInfos = glob->finalrtepermlist;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6265,6 +6269,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6392,6 +6397,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rtepermlist, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1cb0abdbc1..e9e30617ad 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -78,6 +79,13 @@ typedef struct
 	int			newvarno;
 } fix_windowagg_cond_context;
 
+/* Context info for flatten_rtes_walker() */
+typedef struct
+{
+	PlannerGlobal *glob;
+	Query	   *query;
+} flatten_rtes_walker_context;
+
 /*
  * Selecting the best alternative in an AlternativeSubPlan expression requires
  * estimating how many times that expression will be evaluated.  For an
@@ -113,8 +121,9 @@ typedef struct
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
-static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
+static bool flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt);
+static void add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
+								   RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 										  IndexOnlyScan *plan,
@@ -355,6 +364,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rtepermlist.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -375,7 +387,7 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(glob, root->parse->rtepermlist, rte);
 	}
 
 	/*
@@ -442,18 +454,21 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 /*
  * Extract RangeTblEntries from a subquery that was never planned at all
  */
+
 static void
 flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 {
+	flatten_rtes_walker_context cxt = {glob, rte->subquery};
+
 	/* Use query_tree_walker to find all RTEs in the parse tree */
 	(void) query_tree_walker(rte->subquery,
 							 flatten_rtes_walker,
-							 (void *) glob,
+							 (void *) &cxt,
 							 QTW_EXAMINE_RTES_BEFORE);
 }
 
 static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
+flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt)
 {
 	if (node == NULL)
 		return false;
@@ -463,33 +478,38 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(cxt->glob, cxt->query->rtepermlist, rte);
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into subselects */
+		/*
+		 * Recurse into subselects.  Must update cxt->query to this query so
+		 * that the rtable and rtepermlist correspond with each other.
+		 */
+		cxt->query = (Query *) node;
 		return query_tree_walker((Query *) node,
 								 flatten_rtes_walker,
-								 (void *) glob,
+								 (void *) cxt,
 								 QTW_EXAMINE_RTES_BEFORE);
 	}
 	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+								  (void *) cxt);
 }
 
 /*
- * Add (a copy of) the given RTE to the final rangetable
+ * Add (a copy of) the given RTE to the final rangetable and also the
+ * corresponding RTEPermissionInfo, if any, to final rtepermlist.
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo.
  */
 static void
-add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
+add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
+					   RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
 
@@ -527,6 +547,20 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 */
 	if (newrte->rtekind == RTE_RELATION)
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+	/*
+	 * Add the RTEPermissionInfo, if any, corresponding to this RTE to the
+	 * flattened global list.  Also update the perminfoindex in newrte to
+	 * reflect the RTEPermissionInfo's position in this other list.
+	 */
+	if (rte->perminfoindex > 0)
+	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = GetRTEPermissionInfo(rtepermlist, newrte);
+		glob->finalrtepermlist = lappend(glob->finalrtepermlist, perminfo);
+		newrte->perminfoindex = list_length(glob->finalrtepermlist);
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..bee4072301 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,8 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
-	/* Now we can attach the modified subquery rtable to the parent */
-	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	/*
+	 * Now we can attach the modified subquery rtable to the parent. This also
+	 * adds subquery's RTEPermissionInfos into the upper query.
+	 */
+	parse->rtable = CombineRangeTables(parse->rtable, subselect->rtable,
+									   subselect->rtepermlist,
+									   &parse->rtepermlist);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index f4cdb879c2..f70b0c9862 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1209,8 +1202,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
 	 * code on the subquery ones too.)
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtable = list_concat(parse->rtable, subquery->rtable);
+	parse->rtable = CombineRangeTables(parse->rtable, subquery->rtable,
+									   subquery->rtepermlist,
+									   &parse->rtepermlist);
 
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
@@ -1347,8 +1344,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 
 	/*
 	 * Append child RTEs to parent rtable.
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->rtable = CombineRangeTables(root->parse->rtable, rtable,
+											 subquery->rtepermlist,
+											 &root->parse->rtepermlist);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..b22422b166 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -47,6 +49,10 @@ static void expand_single_inheritance_child(PlannerInfo *root,
 											Index *childRTindex_p);
 static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 									  List *translated_vars);
+static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
+												 RelOptInfo *rel,
+												 RelOptInfo *top_parent_rel,
+												 Bitmapset *top_parent_cols);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
 									  RangeTblEntry *rte, Index rti);
 
@@ -131,6 +137,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +151,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +317,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +337,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +414,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset  *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +473,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,9 +496,16 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
+	/*
+	 * No permission checking for the child RTE unless it's the parent
+	 * relation in its child role, which only applies to traditional
+	 * inheritance.
+	 */
+	if (childOID != parentOID)
+		childrte->perminfoindex = 0;
+
 	/* Link not-yet-fully-filled child RTE into data structures */
 	parse->rtable = lappend(parse->rtable, childrte);
 	childRTindex = list_length(parse->rtable);
@@ -539,33 +566,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -648,6 +654,54 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 }
 
+/*
+ * get_rel_all_updated_cols
+ * 		Returns the set of columns of a given "simple" relation that are
+ * 		updated by this query.
+ */
+Bitmapset *
+get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index		relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset  *updatedCols,
+			   *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+	Assert(IS_SIMPLE_REL(rel));
+
+	/*
+	 * We obtain updatedCols and extraUpdatedCols for the query's result
+	 * relation.  Then, if necessary, we map it to the column numbers of the
+	 * relation for which they were requested.
+	 */
+	relid = root->parse->resultRelation;
+	rte = planner_rt_fetch(relid, root);
+	perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+
+	updatedCols = perminfo->updatedCols;
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	/*
+	 * For "other" rels, we must look up the root parent relation mentioned in
+	 * the query, and translate the column numbers.
+	 */
+	if (rel->relid != relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, relid);
+
+		Assert(IS_OTHER_REL(rel));
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  extraUpdatedCols);
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
+
 /*
  * translate_col_privs
  *	  Translate a bitmapset representing per-column privileges from the
@@ -866,3 +920,40 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset  *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7b4434e7f..800fe490d9 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -223,7 +224,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though only
+		 * the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo;
+
+			perminfo = GetRTEPermissionInfo(root->parse->rtepermlist, rte);
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..40473fe86f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rtepermlist;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,17 +596,19 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
-	 * The SELECT's joinlist is not affected however.  We must do this before
-	 * adding the target table to the INSERT's rtable.
+	 * INSERT/SELECT, arrange to pass the rangetable/rtepermlist/namespace
+	 * down to the SELECT.  This can only happen if we are inside a CREATE
+	 * RULE, and in that case we want the rule's OLD and NEW rtable entries to
+	 * appear as part of the SELECT's rtable, not as outer references for it.
+	 * (Kluge!) The SELECT's joinlist is not affected however.  We must do
+	 * this before adding the target table to the INSERT's rtable.
 	 */
 	if (isGeneralSelect)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rtepermlist = pstate->p_rtepermlist;
+		pstate->p_rtepermlist = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rtepermlist = sub_rtepermlist;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 62c2ff69f0..225bf7561e 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -215,6 +215,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rtepermlist = pstate->p_rtepermlist;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -287,7 +288,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -346,7 +347,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -360,8 +361,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4665f0b2b7..fab8083dbb 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1037,11 +1037,15 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo;
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo = GetRTEPermissionInfo(pstate->p_rtepermlist, rte);
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
-										   col - FirstLowInvalidHeapAttributeNumber);
+		perminfo->selectedCols =
+			bms_add_member(perminfo->selectedCols,
+						   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
@@ -1251,10 +1255,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1290,6 +1297,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1433,6 +1441,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1469,7 +1478,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1478,12 +1487,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1497,7 +1502,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1534,6 +1539,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1557,7 +1563,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1566,12 +1572,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rtepermlist, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1585,7 +1587,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1659,21 +1661,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1990,20 +1986,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2015,7 +2004,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2082,20 +2071,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2170,19 +2152,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2267,19 +2243,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2294,6 +2264,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2417,19 +2388,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2543,16 +2508,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2564,7 +2526,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3189,6 +3151,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3206,7 +3169,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3855,3 +3821,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rtepermlist = lappend(*rtepermlist, perminfo);
+
+	/* Note its index (1-based!) */
+	rte->perminfoindex = list_length(*rtepermlist);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rtepermlist, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	if (rte->perminfoindex == 0 ||
+		rte->perminfoindex > list_length(rtepermlist))
+		elog(ERROR, "invalid perminfoindex %d in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = list_nth_node(RTEPermissionInfo, rtepermlist,
+							 rte->perminfoindex - 1);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match provided RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 8e0d6fd01f..56d64c8851 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 36791d8817..efff8c03b3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3023,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3081,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rtepermlist = pstate->p_rtepermlist;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3123,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e48a3f589a..568344cc23 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rtepermlist, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rtepermlist, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index db45d8a08b..3b2649f7a0 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -797,14 +797,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -831,18 +831,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rtepermlist)
+	{
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..ecf585e0fd 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *action_rtepermlist;
 
 	context.for_execute = true;
 
@@ -395,32 +396,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its rtepermlist must be preserved so that the executor will
+	 * do the correct permissions checks on the relations referenced in it.
+	 * This allows us to check that the caller has, say, insert-permission on
+	 * a view, when the view is not semantically referenced at all in the
+	 * resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rtepermlist,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	action_rtepermlist = sub_action->rtepermlist;
+	sub_action->rtepermlist = copyObject(parsetree->rtepermlist);
+	sub_action->rtable = CombineRangeTables(copyObject(parsetree->rtable),
+											sub_action->rtable,
+											action_rtepermlist,
+											&sub_action->rtepermlist);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1628,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1657,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1743,6 +1750,8 @@ ApplyRetrieveRule(Query *parsetree,
 	Query	   *rule_action;
 	RangeTblEntry *rte,
 			   *subrte;
+	RTEPermissionInfo *perminfo,
+			   *sub_perminfo;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1792,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1855,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1872,6 +1863,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rte);
 
 	rte->rtekind = RTE_SUBQUERY;
 	rte->subquery = rule_action;
@@ -1881,6 +1873,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;		/* no permission checking for this RTE */
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
@@ -1889,19 +1882,12 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	sub_perminfo = GetRTEPermissionInfo(rule_action->rtepermlist, subrte);
+	sub_perminfo->requiredPerms = perminfo->requiredPerms;
+	sub_perminfo->checkAsUser = perminfo->checkAsUser;
+	sub_perminfo->selectedCols = perminfo->selectedCols;
+	sub_perminfo->insertedCols = perminfo->insertedCols;
+	sub_perminfo->updatedCols = perminfo->updatedCols;
 
 	return parsetree;
 }
@@ -1931,8 +1917,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rtepermlist, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3063,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3202,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rtepermlist, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3274,68 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * rtepermlist so that the executor still performs appropriate permissions
+	 * checks for the query caller's use of the view.
+	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, view_rte);
+
+	/*
+	 * Disregard the perminfo in viewquery->rtepermlist that the base_rte
+	 * would currently be pointing at, because we'd like it to point now
+	 * to a new one that will be filled below.  Must set perminfoindex to
+	 * 0 to not trip over the Assert in AddRTEPermissionInfo().
 	 */
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rtepermlist, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Initially, new_perminfo (base_perminfo) contains selectedCols permission
+	 * check bits for all base-rel columns referenced by the view, but since
+	 * the view is a SELECT query its insertedCols/updatedCols is empty.  We
+	 * set insertedCols and updatedCols to include all the columns the outer
+	 * query is trying to modify, adjusting the column numbers as needed.  But
+	 * we leave selectedCols as-is, so the view owner must have read permission
+	 * for all columns used in the view definition, even if some of them are
+	 * not read by the outer query.  We could try to limit selectedCols to only
+	 * columns used in the transformed query, but that does not correspond to
+	 * what happens in ordinary SELECT usage of a view: all referenced columns
+	 * must have read permission, even if optimization finds that some of them
+	 * can be discarded during query transformation.  The flattening we're
+	 * doing here is an optional optimization, too.  (If you are unpersuaded
+	 * and want to change this, note that applying adjust_view_column_set to
+	 * view_perminfo->selectedCols is clearly *not* the right answer, since
+	 * that neglects base-rel columns used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
+
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3439,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3450,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
+		/* Ignore the RTEPermissionInfo that would've been added. */
+		new_exclRte->perminfoindex = 0;
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3725,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3737,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rtepermlist, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3824,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..3577c7f335 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,37 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * CombineRangeTables
+ * 		Adds the RTEs of 'rtable2' into 'rtable1'
+ *
+ * This also adds the RTEPermissionInfos of 'rtepermlist2' (belonging to the
+ * RTEs in 'rtable2') into *rtepermlist1 and also updates perminfoindex of the
+ * RTEs in 'rtable2' to now point to the perminfos' indexes in *rtepermlist1.
+ *
+ * Note that this changes both 'rtable1' and 'rtepermlist1' destructively, so
+ * the caller should have better passed safe-to-modify copies.
+ */
+List *
+CombineRangeTables(List *rtable1, List *rtable2,
+				   List *rtepermlist2, List **rtepermlist1)
+{
+	ListCell   *l;
+	int			offset = list_length(*rtepermlist1);
+
+	if (offset > 0)
+	{
+		foreach(l, rtable2)
+		{
+			RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += offset;
+		}
+	}
+
+	*rtepermlist1 = list_concat(*rtepermlist1, rtepermlist2);
+
+	return list_concat(rtable1, rtable2);
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b2a7237430..e4ce49d606 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,20 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rtepermlist, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	user_id = perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +202,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +249,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +292,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +348,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +377,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +480,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc07157037..29f29d664b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..c3feacb4f8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in
+		 * case it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..3d2de0ae1b 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rtepermlist;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 5c02a1521f..6f5a7038e1 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -196,7 +196,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+								 List *rtepermlist, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -576,6 +577,7 @@ exec_rt_fetch(Index rti, EState *estate)
 }
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
+extern RTEPermissionInfo *ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 								   Index rti);
 
@@ -601,8 +603,9 @@ extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
 extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
-					  EState *estate);
+									  EState *estate);
 
+extern Oid	ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate);
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 313840fe32..0e6c940ed5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -568,7 +568,7 @@ typedef struct ResultRelInfo
 	 * describe a given child table's columns; see ExecGetInsertedCols() et
 	 * al.  Like ri_ChildToRootMap, computed only if needed.
 	 */
-	AttrMap	   *ri_RootToChildMap;
+	AttrMap    *ri_RootToChildMap;
 	bool		ri_RootToChildMapValid;
 
 	/* for use by copyfrom.c when performing multi-inserts */
@@ -621,6 +621,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rtepermlist; /* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6112cd85c8..c8a0d6cacc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -154,6 +154,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rtepermlist;	/* list of RTEPermissionInfo nodes for the
+								 * rtable entries having perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -968,37 +970,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1054,11 +1025,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the containing struct's list of same; 0 if permissions need
+	 * not be checked for this RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1178,14 +1154,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns they need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index ef95429a0d..27618918b4 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrtepermlist;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 61cae463fb..f5e3025e27 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -72,6 +72,9 @@ typedef struct PlannedStmt
 
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
+	List	   *permInfos;		/* list of RTEPermissionInfo nodes for rtable
+								 * entries needing one */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..8ebd42b757 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -20,6 +20,8 @@
 extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 									 RangeTblEntry *rte, Index rti);
 
+extern Bitmapset *get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel);
+
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..7aebed5863 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rtepermlist: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rtepermlist;	/* list of RTEPermissionInfo nodes for each
+								 * RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rtepermlist.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rtepermlist entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..3cf475513b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rtepermlist,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rtepermlist,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..2f56f7a6ac 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,8 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern List *CombineRangeTables(List *rtable1, List *rtable2,
+								List *rtepermlist2,
+								List **rtepermlist1);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..bfa9263233 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rtepermlist, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rtepermlist, do_abort))
 		allow = false;
 
 	if (allow)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 37c1c86473..e1cdaf9eb6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3584,6 +3584,18 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 RESET SESSION AUTHORIZATION;
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+ERROR:  permission denied for table ruletest_t3
+RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
  x 
 ---
@@ -3596,6 +3608,8 @@ SELECT * FROM ruletest_t2;
 (1 row)
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 DROP USER regress_rule_user1;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index bfb5f3b0bb..2f7cb8482a 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1309,11 +1309,26 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 
+RESET SESSION AUTHORIZATION;
+
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+
 RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
 SELECT * FROM ruletest_t2;
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2f5802195d..1d0134db8d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2188,6 +2188,7 @@ RI_ConstraintInfo
 RI_QueryHashEntry
 RI_QueryKey
 RTEKind
+RTEPermissionInfo
 RWConflict
 RWConflictPoolHeader
 Range
@@ -3265,6 +3266,7 @@ fix_scan_expr_context
 fix_upper_expr_context
 fix_windowagg_cond_context
 flatten_join_alias_vars_context
+flatten_rtes_walker_context
 float4
 float4KEY
 float8
-- 
2.35.3



  [application/octet-stream] v29-0002-Stop-accessing-checkAsUser-via-RTE-in-some-cases.patch (9.6K, ../../CA+HiwqGFCs2uq7VRKi7g+FFKbP6Ea_2_HkgZb2HPhUfaAKT3ng@mail.gmail.com/3-v29-0002-Stop-accessing-checkAsUser-via-RTE-in-some-cases.patch)
  download | inline diff:
From 295b6c97400a308eaab2484eb32cc27ed6b43918 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Tue, 29 Nov 2022 16:14:57 +0900
Subject: [PATCH v29 2/5] Stop accessing checkAsUser via RTE in some cases

A future commit will move the checkAsUser field from RangeTblEntry
to a new node that, unlike RTEs, will only be created for tables
mentioned in the query but not for the inheritance child relations
added to the query by the planner.  So, checkAsUser value for a
given child relation will have to be obtained by referring to that
for its ancestor mentioned in the query.

In preparation, it seems better to expand the use of RelOptInfo.userid
during planning in place of rte->checkAsUser so that there will be
fewer places to adjust for the above change.

Given that the child-to-ancestor mapping is not available during the
execution of a given "child" ForeignScan node, add a checkAsUser
field to ForeignScan to carry the child relation's RelOptInfo.userid.
---
 contrib/postgres_fdw/postgres_fdw.c     | 15 +++++----------
 src/backend/optimizer/plan/createplan.c |  6 +++++-
 src/backend/statistics/extended_stats.c |  6 +++---
 src/backend/utils/adt/selfuncs.c        | 13 +++++++------
 src/include/nodes/pathnodes.h           |  2 +-
 src/include/nodes/plannodes.h           |  2 ++
 6 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8d7500abfb..afe234ea66 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -624,7 +624,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 {
 	PgFdwRelationInfo *fpinfo;
 	ListCell   *lc;
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
@@ -663,7 +662,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
-		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+		Oid			userid = baserel->userid ? baserel->userid : GetUserId();
 
 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
 	}
@@ -1510,16 +1509,14 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.  In case of a join or aggregate, use the
-	 * lowest-numbered member RTE as a representative; we would get the same
-	 * result from any.
+	 * ExecCheckRTEPerms() does.
 	 */
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 	if (fsplan->scan.scanrelid > 0)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
 	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(rte->relid);
@@ -2633,7 +2630,6 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	EState	   *estate = node->ss.ps.state;
 	PgFdwDirectModifyState *dmstate;
 	Index		rtindex;
-	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
 	UserMapping *user;
@@ -2655,11 +2651,10 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	 * Identify which user to do the remote access as.  This should match what
 	 * ExecCheckRTEPerms() does.
 	 */
-	rtindex = node->resultRelInfo->ri_RangeTableIndex;
-	rte = exec_rt_fetch(rtindex, estate);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = fsplan->checkAsUser ? fsplan->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
+	rtindex = node->resultRelInfo->ri_RangeTableIndex;
 	if (fsplan->scan.scanrelid == 0)
 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
 	else
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ac86ce9003..5013ac3377 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4148,6 +4148,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	/* Copy user OID to access as; likewise no need to make FDW do this */
+	scan_plan->checkAsUser = rel->userid;
+
 	/* Copy foreign server OID; likewise, no need to make FDW do this */
 	scan_plan->fs_server = rel->serverid;
 
@@ -5794,7 +5797,8 @@ make_foreignscan(List *qptlist,
 	node->operation = CMD_SELECT;
 	node->resultRelation = 0;
 
-	/* fs_server will be filled in by create_foreignscan_plan */
+	/* checkAsUser, fs_server will be filled in by create_foreignscan_plan */
+	node->checkAsUser = InvalidOid;
 	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ab97e71dd7..88f03bb5a9 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1598,6 +1598,7 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 							 Bitmapset **attnums, List **exprs)
 {
 	RangeTblEntry *rte = root->simple_rte_array[relid];
+	RelOptInfo *rel = root->simple_rel_array[relid];
 	RestrictInfo *rinfo;
 	int			clause_relid;
 	Oid			userid;
@@ -1646,10 +1647,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 		return false;
 
 	/*
-	 * Check that the user has permission to read all required attributes. Use
-	 * checkAsUser if it's set, in case we're accessing the table via a view.
+	 * Check that the user has permission to read all required attributes.
 	 */
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	userid = rel->userid ? rel->userid : GetUserId();
 
 	/* Table-level SELECT privilege is sufficient for all columns */
 	if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index f116924d3c..eabb79db1d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5158,7 +5158,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 								 * Use checkAsUser if it's set, in case we're
 								 * accessing the table via a view.
 								 */
-								userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+								userid = onerel->userid ? onerel->userid : GetUserId();
 
 								/*
 								 * For simplicity, we insist on the whole
@@ -5210,7 +5210,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+										userid = onerel->userid ? onerel->userid : GetUserId();
 
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
@@ -5293,7 +5293,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Use checkAsUser if it's set, in case we're accessing
 					 * the table via a view.
 					 */
-					userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+					userid = onerel->userid ? onerel->userid : GetUserId();
 
 					/*
 					 * For simplicity, we insist on the whole table being
@@ -5341,7 +5341,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+							userid = onerel->userid ? onerel->userid : GetUserId();
 
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
@@ -5402,6 +5402,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
+			RelOptInfo *onerel = find_base_rel(root, var->varno);
 			Oid			userid;
 
 			/*
@@ -5410,7 +5411,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * from security barrier views or RLS policies.  Use checkAsUser
 			 * if it's set, in case we're accessing the table via a view.
 			 */
-			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+			userid = onerel->userid ? onerel->userid : GetUserId();
 
 			vardata->acl_ok =
 				rte->securityQuals == NIL &&
@@ -5479,7 +5480,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+				userid = onerel->userid ? onerel->userid : GetUserId();
 
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a544b313d3..ef95429a0d 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -901,7 +901,7 @@ typedef struct RelOptInfo
 	 */
 	/* identifies server for the table or join */
 	Oid			serverid;
-	/* identifies user to check access as */
+	/* identifies user to check access as; 0 means to check as current user */
 	Oid			userid;
 	/* join is only valid for current user */
 	bool		useridiscurrent;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 5c2ab1b379..61cae463fb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -703,6 +703,8 @@ typedef struct ForeignScan
 	Scan		scan;
 	CmdType		operation;		/* SELECT/INSERT/UPDATE/DELETE */
 	Index		resultRelation; /* direct modification target's RT index */
+	Oid			checkAsUser;	/* user to perform the scan as; 0 means to
+								 * check as current user */
 	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
-- 
2.35.3



  [application/octet-stream] v29-0001-Add-ri_RootToChildMap-and-ExecGetRootToChildMap.patch (5.0K, ../../CA+HiwqGFCs2uq7VRKi7g+FFKbP6Ea_2_HkgZb2HPhUfaAKT3ng@mail.gmail.com/4-v29-0001-Add-ri_RootToChildMap-and-ExecGetRootToChildMap.patch)
  download | inline diff:
From 672d4191115e07cdfb7db751f99abdfc04a9e662 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Mon, 28 Nov 2022 16:12:15 +0900
Subject: [PATCH v29 1/5] Add ri_RootToChildMap and ExecGetRootToChildMap()

It's a AttrMap provided for converting "root" table column bitmapsets
into their child relation counterpart, as a more generalized
alternative to using ri_RootToPartitionMap.attrMap to do the same.
More generalized in the sense that it can also be requested for
regular inheritance child relations, whereas ri_RootToPartitionMap
is currently only initialized in tuple-routing "partition" result
relations.

One of the differences between the two cases is that the regular
inheritance child relations can have their own columns that are not
present in the "root" table, so the map must be created in a way that
ignores such columns.  To that end, ExecGetRootToChildMap() passes
true for the missing_ok argument of build_attrmap_by_name(), so that
it puts 0 (InvalidAttr) in the map for the columns of a child table
that are not present in the root table.

root-table-to-child-table bitmapset conversions that would need
ri_RootToChildMap (cannot be done with ri_RootToPartitionMap) are
as of this commit unnecessary, but will become necessary in a
subsequent commit that will remove the insertedCols et al bitmapset
fields from RangeTblEntry node in favor of a new type of node that
will only be created and added to the plan for root tables
in a query and never for children.  The child table bitmapsets will
be created on-the-fly during execution if needed, by copying the
root table bitmapset and converting with the aforementioned map as
required.
---
 src/backend/executor/execUtils.c | 39 ++++++++++++++++++++++++++++++++
 src/include/executor/executor.h  |  2 ++
 src/include/nodes/execnodes.h    |  8 +++++++
 3 files changed, 49 insertions(+)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 0e595ffa6e..e2b4272d90 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1252,6 +1252,45 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
+/*
+ * Return the map needed to convert "root" table column bitmapsets to the
+ * rowtype of an individual child table.  A NULL result is valid and means
+ * that no conversion is needed.
+ */
+AttrMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate)
+{
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToChildMapValid)
+	{
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		if (rootRelInfo)
+		{
+			/*
+			 * Passing 'true' below means any columns present in the child
+			 * table but not in the root parent are to be ignored; note that
+			 * such a case is possible with traditional inheritance but never
+			 * with partitioning.
+			 */
+			resultRelInfo->ri_RootToChildMap =
+				build_attrmap_by_name_if_req(RelationGetDescr(rootRelInfo->ri_RelationDesc),
+											 RelationGetDescr(resultRelInfo->ri_RelationDesc),
+											 true);
+		}
+		else					/* this isn't a child result rel */
+			resultRelInfo->ri_RootToChildMap = NULL;
+
+		resultRelInfo->ri_RootToChildMapValid = true;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	return resultRelInfo->ri_RootToChildMap;
+}
+
 /* Return a bitmap representing columns being inserted */
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ed95ed1176..5c02a1521f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -600,6 +600,8 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern AttrMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo,
+					  EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18e572f171..313840fe32 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -563,6 +563,14 @@ typedef struct ResultRelInfo
 	TupleConversionMap *ri_ChildToRootMap;
 	bool		ri_ChildToRootMapValid;
 
+	/*
+	 * Map used to convert "root" table column bitmapsets into the ones that
+	 * describe a given child table's columns; see ExecGetInsertedCols() et
+	 * al.  Like ri_ChildToRootMap, computed only if needed.
+	 */
+	AttrMap	   *ri_RootToChildMap;
+	bool		ri_RootToChildMapValid;
+
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-29 18:04  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  3 siblings, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2022-11-29 18:04 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-Nov-29, Amit Langote wrote:

> Maybe, we should have the following there so that the PlannedStmt's
> contents don't point into the Query?
> 
>     newperminfo = copyObject(perminfo);

Hmm, I suppose if we want a separate RTEPermissionInfo node, we should
instead do GetRTEPermissionInfo(rte) followed by
AddRTEPermissionInfo(newrte) and avoid the somewhat cowboy-ish coding
there.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-30 02:56  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-11-30 02:56 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Nov 30, 2022 at 3:04 AM Alvaro Herrera <[email protected]> wrote:
> On 2022-Nov-29, Amit Langote wrote:
>
> > Maybe, we should have the following there so that the PlannedStmt's
> > contents don't point into the Query?
> >
> >     newperminfo = copyObject(perminfo);
>
> Hmm, I suppose if we want a separate RTEPermissionInfo node, we should
> instead do GetRTEPermissionInfo(rte) followed by
> AddRTEPermissionInfo(newrte) and avoid the somewhat cowboy-ish coding
> there.

OK, something like the attached?

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] add_rteperminfo_to_flat_rtepermlist.patch (1.5K, ../../CA+HiwqEXtRSGMfuucgtCETbehdvwoW_63EpqgNAk07mAdTHP0g@mail.gmail.com/2-add_rteperminfo_to_flat_rtepermlist.patch)
  download | inline diff:
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e9e30617ad..a843ed3ebd 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -549,17 +549,26 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, List *rtepermlist,
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
 
 	/*
-	 * Add the RTEPermissionInfo, if any, corresponding to this RTE to the
-	 * flattened global list.  Also update the perminfoindex in newrte to
-	 * reflect the RTEPermissionInfo's position in this other list.
+	 * Add a copy of the RTEPermissionInfo, if any, corresponding to this RTE
+	 * to the flattened global list.
 	 */
 	if (rte->perminfoindex > 0)
 	{
 		RTEPermissionInfo *perminfo;
+		RTEPermissionInfo *newperminfo;
 
+		/* Get the existing one from this query's rtepermlist. */
 		perminfo = GetRTEPermissionInfo(rtepermlist, newrte);
-		glob->finalrtepermlist = lappend(glob->finalrtepermlist, perminfo);
-		newrte->perminfoindex = list_length(glob->finalrtepermlist);
+
+		/*
+		 * Add a new one to finalrtepermlist and copy the contents of the
+		 * existing one into it.  Note that AddRTEPermissionInfo() also
+		 * updates newrte->perminfoindex to point to newperminfo in
+		 * finalrtepermlist.
+		 */
+		newrte->perminfoindex = 0;	/* expected by AddRTEPermissionInfo() */
+		newperminfo = AddRTEPermissionInfo(&glob->finalrtepermlist, newrte);
+		memcpy(newperminfo, perminfo, sizeof(RTEPermissionInfo));
 	}
 }
 


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-30 06:45  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-11-30 06:45 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Nov 30, 2022 at 11:56 AM Amit Langote <[email protected]> wrote:
> On Wed, Nov 30, 2022 at 3:04 AM Alvaro Herrera <[email protected]> wrote:
> > On 2022-Nov-29, Amit Langote wrote:
> >
> > > Maybe, we should have the following there so that the PlannedStmt's
> > > contents don't point into the Query?
> > >
> > >     newperminfo = copyObject(perminfo);
> >
> > Hmm, I suppose if we want a separate RTEPermissionInfo node, we should
> > instead do GetRTEPermissionInfo(rte) followed by
> > AddRTEPermissionInfo(newrte) and avoid the somewhat cowboy-ish coding
> > there.
>
> OK, something like the attached?

Thinking more about the patch I sent, which has this:

+       /* Get the existing one from this query's rtepermlist. */
        perminfo = GetRTEPermissionInfo(rtepermlist, newrte);
-       glob->finalrtepermlist = lappend(glob->finalrtepermlist, perminfo);
-       newrte->perminfoindex = list_length(glob->finalrtepermlist);
+
+       /*
+        * Add a new one to finalrtepermlist and copy the contents of the
+        * existing one into it.  Note that AddRTEPermissionInfo() also
+        * updates newrte->perminfoindex to point to newperminfo in
+        * finalrtepermlist.
+        */
+       newrte->perminfoindex = 0;  /* expected by AddRTEPermissionInfo() */
+       newperminfo = AddRTEPermissionInfo(&glob->finalrtepermlist, newrte);
+       memcpy(newperminfo, perminfo, sizeof(RTEPermissionInfo));

Note that simple memcpy'ing would lead to the selectedCols, etc.
bitmapsets being shared between the Query and the PlannedStmt, which
may be considered as not good.  But maybe that's fine, because the
same is true for RangeTblEntry members that do have substructure such
as the various Alias fields that are not reset?  Code paths that like
to keep a PlannedStmt to be decoupled from the corresponding Query,
such as plancache.c, do copy the former, so shared sub-structure in
the default case may be fine after all.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-11-30 08:32  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  3 siblings, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2022-11-30 08:32 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hello

On 2022-Nov-29, Amit Langote wrote:

> Thanks for taking a look and all the fixup patches.  Was working on
> that test I said we should add and then was spending some time
> cleaning things up and breaking some things out into their patches,
> mainly for the ease of review.

Right, excellent.  Thanks for this new version.  It looks pretty good to
me now.

> On Tue, Nov 29, 2022 at 6:27 PM Alvaro Herrera <[email protected]> wrote:

> > The other changes are cosmetic.
> 
> Thanks, I've merged all.  I do wonder that it is only in PlannedStmt
> that the list is called something that is not "rtepermlist", but I'm
> fine with it if you prefer that.

I was unsure about that one myself; I just changed it because that
struct uses camelCaseNaming, which the others do not, so it seemed fine
in the other places but not there.  As for changing "list" to "infos",
it seems to me we tend to avoid naming a list as "list", so.  (Maybe I
would change the others to be foo_rteperminfos.  Unless these naming
choices were already bikeshedded to its present form upthread and I
missed it?)

> As I mentioned above, I've broken a couple of other changes out into
> their own patches that I've put before the main patch.  0001 adds
> ExecGetRootToChildMap().  I thought it would be better to write in the
> commit message why the new map is necessary for the main patch.

I was thinking about this one and it seemed too closely tied to
ExecGetInsertedCols to be committed separately.  Notice how there is a
comment that mentions that function in your 0001, but that function
itself still uses ri_RootToPartitionMap, so before your 0003 the comment
is bogus.  And there's now quite some duplicity between
ri_RootToPartitionMap and ri_RootToChildMap, which I think it would be
better to reduce.  I mean, rather than add a new field it would be
better to repurpose the old one:

- ExecGetRootToChildMap should return TupleConversionMap *
- every place that accesses ri_RootToPartitionMap directly should be
  using ExecGetRootToChildMap() instead
- ExecGetRootToChildMap passes build_attrmap_by_name_if_req
  !resultRelInfo->ri_RelationDesc->rd_rel->relispartition
  as third argument to build_attrmap_by_name_if_req (rather than
  constant true), so that we keep the tuple compatibility checking we
  have there currently.


> 0002 contains changes that has to do with changing how we access
> checkAsUser in some foreign table planning/execution code sites.
> Thought it might be better to describe it separately too.

I'll get this one pushed soon, it seems good to me.  (I'll edit to not
use Oid as boolean.)

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-01 10:49  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  3 siblings, 0 replies; 73+ messages in thread

From: Alvaro Herrera @ 2022-12-01 10:49 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hello,

This didn't apply, so I rebased it on current master, excluding the one
I already pushed.  No further changes.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/
"No me acuerdo, pero no es cierto.  No es cierto, y si fuera cierto,
 no me acuerdo."                 (Augusto Pinochet a una corte de justicia)


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-02 07:41  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Amit Langote @ 2022-12-02 07:41 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi Alvaro,

On Wed, Nov 30, 2022 at 5:32 PM Alvaro Herrera <[email protected]> wrote:
> > On Tue, Nov 29, 2022 at 6:27 PM Alvaro Herrera <[email protected]> wrote:
> > Thanks, I've merged all.  I do wonder that it is only in PlannedStmt
> > that the list is called something that is not "rtepermlist", but I'm
> > fine with it if you prefer that.
>
> I was unsure about that one myself; I just changed it because that
> struct uses camelCaseNaming, which the others do not, so it seemed fine
> in the other places but not there.  As for changing "list" to "infos",
> it seems to me we tend to avoid naming a list as "list", so.  (Maybe I
> would change the others to be foo_rteperminfos.  Unless these naming
> choices were already bikeshedded to its present form upthread and I
> missed it?)

No, I think it was I who came up with the "..list" naming and
basically just stuck with it.

Actually, I don't mind changing to "...infos", which I have done in
the attached updated patch.

> > As I mentioned above, I've broken a couple of other changes out into
> > their own patches that I've put before the main patch.  0001 adds
> > ExecGetRootToChildMap().  I thought it would be better to write in the
> > commit message why the new map is necessary for the main patch.
>
> I was thinking about this one and it seemed too closely tied to
> ExecGetInsertedCols to be committed separately.  Notice how there is a
> comment that mentions that function in your 0001, but that function
> itself still uses ri_RootToPartitionMap, so before your 0003 the comment
> is bogus.  And there's now quite some duplicity between
> ri_RootToPartitionMap and ri_RootToChildMap, which I think it would be
> better to reduce.  I mean, rather than add a new field it would be
> better to repurpose the old one:
>
> - ExecGetRootToChildMap should return TupleConversionMap *
> - every place that accesses ri_RootToPartitionMap directly should be
>   using ExecGetRootToChildMap() instead
> - ExecGetRootToChildMap passes build_attrmap_by_name_if_req
>   !resultRelInfo->ri_RelationDesc->rd_rel->relispartition
>   as third argument to build_attrmap_by_name_if_req (rather than
>   constant true), so that we keep the tuple compatibility checking we
>   have there currently.

This sounds like a better idea than adding a new AttrMap, so done this
way in the attached 0001.

> > 0002 contains changes that has to do with changing how we access
> > checkAsUser in some foreign table planning/execution code sites.
> > Thought it might be better to describe it separately too.
>
> I'll get this one pushed soon, it seems good to me.  (I'll edit to not
> use Oid as boolean.)

Thanks for committing that one.

I've also merged into 0002 the delta patch I had posted earlier to add
a copy of RTEPermInfos into the flattened permInfos list instead of
adding the Query's copy.

--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-02 07:44  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-12-02 07:44 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Dec 2, 2022 at 4:41 PM Amit Langote <[email protected]> wrote:
> Hi Alvaro,
>
> On Wed, Nov 30, 2022 at 5:32 PM Alvaro Herrera <[email protected]> wrote:
> > > On Tue, Nov 29, 2022 at 6:27 PM Alvaro Herrera <[email protected]> wrote:
> > > Thanks, I've merged all.  I do wonder that it is only in PlannedStmt
> > > that the list is called something that is not "rtepermlist", but I'm
> > > fine with it if you prefer that.
> >
> > I was unsure about that one myself; I just changed it because that
> > struct uses camelCaseNaming, which the others do not, so it seemed fine
> > in the other places but not there.  As for changing "list" to "infos",
> > it seems to me we tend to avoid naming a list as "list", so.  (Maybe I
> > would change the others to be foo_rteperminfos.  Unless these naming
> > choices were already bikeshedded to its present form upthread and I
> > missed it?)
>
> No, I think it was I who came up with the "..list" naming and
> basically just stuck with it.
>
> Actually, I don't mind changing to "...infos", which I have done in
> the attached updated patch.
>
> > > As I mentioned above, I've broken a couple of other changes out into
> > > their own patches that I've put before the main patch.  0001 adds
> > > ExecGetRootToChildMap().  I thought it would be better to write in the
> > > commit message why the new map is necessary for the main patch.
> >
> > I was thinking about this one and it seemed too closely tied to
> > ExecGetInsertedCols to be committed separately.  Notice how there is a
> > comment that mentions that function in your 0001, but that function
> > itself still uses ri_RootToPartitionMap, so before your 0003 the comment
> > is bogus.  And there's now quite some duplicity between
> > ri_RootToPartitionMap and ri_RootToChildMap, which I think it would be
> > better to reduce.  I mean, rather than add a new field it would be
> > better to repurpose the old one:
> >
> > - ExecGetRootToChildMap should return TupleConversionMap *
> > - every place that accesses ri_RootToPartitionMap directly should be
> >   using ExecGetRootToChildMap() instead
> > - ExecGetRootToChildMap passes build_attrmap_by_name_if_req
> >   !resultRelInfo->ri_RelationDesc->rd_rel->relispartition
> >   as third argument to build_attrmap_by_name_if_req (rather than
> >   constant true), so that we keep the tuple compatibility checking we
> >   have there currently.
>
> This sounds like a better idea than adding a new AttrMap, so done this
> way in the attached 0001.
>
> > > 0002 contains changes that has to do with changing how we access
> > > checkAsUser in some foreign table planning/execution code sites.
> > > Thought it might be better to describe it separately too.
> >
> > I'll get this one pushed soon, it seems good to me.  (I'll edit to not
> > use Oid as boolean.)
>
> Thanks for committing that one.
>
> I've also merged into 0002 the delta patch I had posted earlier to add
> a copy of RTEPermInfos into the flattened permInfos list instead of
> adding the Query's copy.

Oops, hit send before attaching anything.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v31-0001-Generalize-ri_RootToPartitionMap-to-use-for-non-.patch (13.8K, ../../CA+HiwqGz_CYVpSv47VbFOxpzPKW63MRajRcwH-a9Qv_EEog8JA@mail.gmail.com/2-v31-0001-Generalize-ri_RootToPartitionMap-to-use-for-non-.patch)
  download | inline diff:
From 68853dc0f14760f3b38c8addf16fcd96b835a6f1 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Mon, 28 Nov 2022 16:12:15 +0900
Subject: [PATCH v31 1/2] Generalize ri_RootToPartitionMap to use for
 non-partition children

ri_RootToPartitionMap is currently only initialized for tuple routing
target partitions, though a future commit will need the ability to
use it even for the non-partition child tables, so make adjustments
to the decouple it from the partitioning code.

That includes making all code sites that use ri_RootToPartitionMap to
access it by calling ExecGetRootToChildMap() instead of accessing it
directly.  The function houses the logic of setting the map
appropriately depending on whether a given child relation is partition
or not.
---
 src/backend/access/common/tupconvert.c   | 19 +++++++-
 src/backend/commands/copyfrom.c          |  2 +-
 src/backend/executor/execMain.c          |  8 ++--
 src/backend/executor/execPartition.c     | 27 ++++-------
 src/backend/executor/execUtils.c         | 57 ++++++++++++++++++++----
 src/backend/executor/nodeModifyTable.c   |  2 +-
 src/backend/replication/logical/worker.c |  4 +-
 src/include/access/tupconvert.h          |  3 ++
 src/include/executor/executor.h          |  1 +
 src/include/nodes/execnodes.h            |  9 ++--
 10 files changed, 93 insertions(+), 39 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index b2f892d2fd..16d5664568 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -102,9 +102,7 @@ TupleConversionMap *
 convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc)
 {
-	TupleConversionMap *map;
 	AttrMap    *attrMap;
-	int			n = outdesc->natts;
 
 	/* Verify compatibility and prepare attribute-number map */
 	attrMap = build_attrmap_by_name_if_req(indesc, outdesc, false);
@@ -115,6 +113,23 @@ convert_tuples_by_name(TupleDesc indesc,
 		return NULL;
 	}
 
+	return convert_tuples_by_name_attrmap(indesc, outdesc, attrMap);
+}
+
+/*
+ * Sets up tuple conversion for input and output TupleDescs using given
+ * AttrMap.
+ */
+TupleConversionMap *
+convert_tuples_by_name_attrmap(TupleDesc indesc,
+							   TupleDesc outdesc,
+							   AttrMap *attrMap)
+{
+	int		n = outdesc->natts;
+	TupleConversionMap *map;
+
+	Assert(attrMap != NULL);
+
 	/* Prepare the map structure */
 	map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
 	map->indesc = indesc;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index a079c70152..504afcb811 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1088,7 +1088,7 @@ CopyFrom(CopyFromState cstate)
 			 * We might need to convert from the root rowtype to the partition
 			 * rowtype.
 			 */
-			map = resultRelInfo->ri_RootToPartitionMap;
+			map = ExecGetRootToChildMap(resultRelInfo, estate);
 			if (insertMethod == CIM_SINGLE || !leafpart_use_multi_insert)
 			{
 				/* non batch insert */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7a4db80104..2d68aafc69 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1307,9 +1307,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	 * this field is filled in ExecInitModifyTable().
 	 */
 	resultRelInfo->ri_RootResultRelInfo = partition_root_rri;
-	resultRelInfo->ri_RootToPartitionMap = NULL;	/* set by
-													 * ExecInitRoutingInfo */
-	resultRelInfo->ri_PartitionTupleSlot = NULL;	/* ditto */
+	/* Set by ExecGetRootToChildMap */
+	resultRelInfo->ri_RootToPartitionMap = NULL;
+	resultRelInfo->ri_RootToPartitionMapValid = false;
+	/* Set by ExecInitRoutingInfo */
+	resultRelInfo->ri_PartitionTupleSlot = NULL;
 	resultRelInfo->ri_ChildToRootMap = NULL;
 	resultRelInfo->ri_ChildToRootMapValid = false;
 	resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 13e450c0fa..b0eb15b982 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -469,7 +469,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 			 */
 			if (is_leaf)
 			{
-				TupleConversionMap *map = rri->ri_RootToPartitionMap;
+				TupleConversionMap *map = ExecGetRootToChildMap(rri, estate);
 
 				if (map)
 					slot = execute_attr_map_slot(map->attrMap, rootslot,
@@ -733,7 +733,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 			OnConflictSetState *onconfl = makeNode(OnConflictSetState);
 			TupleConversionMap *map;
 
-			map = leaf_part_rri->ri_RootToPartitionMap;
+			map = ExecGetRootToChildMap(leaf_part_rri, estate);
 
 			Assert(node->onConflictSet != NIL);
 			Assert(rootResultRelInfo->ri_onConflict != NULL);
@@ -983,33 +983,24 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 					int partidx,
 					bool is_borrowed_rel)
 {
-	ResultRelInfo *rootRelInfo = partRelInfo->ri_RootResultRelInfo;
 	MemoryContext oldcxt;
 	int			rri_index;
 
 	oldcxt = MemoryContextSwitchTo(proute->memcxt);
 
 	/*
-	 * Set up a tuple conversion map to convert a tuple routed to the
-	 * partition from the parent's type to the partition's.
+	 * Set up tuple conversion between root parent and the partition if the
+	 * two have different rowtypes.  If conversion is indeed required, also
+	 * initialize a slot dedicated to storing this partition's converted
+	 * tuples.  Various operations that are applied to tuples after routing,
+	 * such as checking constraints, will refer to this slot.
 	 */
-	partRelInfo->ri_RootToPartitionMap =
-		convert_tuples_by_name(RelationGetDescr(rootRelInfo->ri_RelationDesc),
-							   RelationGetDescr(partRelInfo->ri_RelationDesc));
-
-	/*
-	 * If a partition has a different rowtype than the root parent, initialize
-	 * a slot dedicated to storing this partition's tuples.  The slot is used
-	 * for various operations that are applied to tuples after routing, such
-	 * as checking constraints.
-	 */
-	if (partRelInfo->ri_RootToPartitionMap != NULL)
+	if (ExecGetRootToChildMap(partRelInfo, estate) != NULL)
 	{
 		Relation	partrel = partRelInfo->ri_RelationDesc;
 
 		/*
-		 * Initialize the slot itself setting its descriptor to this
-		 * partition's TupleDesc; TupleDesc reference will be released at the
+		 * This pins the partition's TupleDesc, which will be released at the
 		 * end of the command.
 		 */
 		partRelInfo->ri_PartitionTupleSlot =
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index dce93a8c9f..62c8f1688f 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1254,6 +1254,45 @@ ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
 	return resultRelInfo->ri_ChildToRootMap;
 }
 
+/*
+ * Returns the map needed to convert given root result relation's tuples to
+ * the rowtype of the given child relation.  Note that a NULL result is valid
+ * and means that no conversion is needed.
+ */
+TupleConversionMap *
+ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate)
+{
+	/* Mustn't get called for a non-child result relation. */
+	Assert(resultRelInfo->ri_RootResultRelInfo);
+
+	/* If we didn't already do so, compute the map for this child. */
+	if (!resultRelInfo->ri_RootToPartitionMapValid)
+	{
+		ResultRelInfo *rootRelInfo = resultRelInfo->ri_RootResultRelInfo;
+		TupleDesc	indesc = RelationGetDescr(rootRelInfo->ri_RelationDesc);
+		TupleDesc	outdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+		Relation	childrel = resultRelInfo->ri_RelationDesc;
+		AttrMap	   *attrMap;
+		MemoryContext oldcontext;
+
+		/*
+		 * When this child table is not a partition (!relispartition), it may
+		 * have columns that are not present in the root table, which we ask
+		 * to ignore by passing true for missing_ok.
+		 */
+		oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+		attrMap = build_attrmap_by_name_if_req(indesc, outdesc,
+										!childrel->rd_rel->relispartition);
+		if (attrMap)
+			resultRelInfo->ri_RootToPartitionMap =
+				convert_tuples_by_name_attrmap(indesc, outdesc, attrMap);
+		MemoryContextSwitchTo(oldcontext);
+		resultRelInfo->ri_RootToPartitionMapValid = true;
+	}
+
+	return resultRelInfo->ri_RootToPartitionMap;
+}
+
 /* Return a bitmap representing columns being inserted */
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
@@ -1274,10 +1313,10 @@ ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
 		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->insertedCols);
+		if (map != NULL)
+			return execute_attr_map_cols(map->attrMap, rte->insertedCols);
 		else
 			return rte->insertedCols;
 	}
@@ -1308,10 +1347,10 @@ ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
 		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->updatedCols);
+		if (map != NULL)
+			return execute_attr_map_cols(map->attrMap, rte->updatedCols);
 		else
 			return rte->updatedCols;
 	}
@@ -1334,10 +1373,10 @@ ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 	{
 		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
 		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
+		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (relinfo->ri_RootToPartitionMap != NULL)
-			return execute_attr_map_cols(relinfo->ri_RootToPartitionMap->attrMap,
-										 rte->extraUpdatedCols);
+		if (map != NULL)
+			return execute_attr_map_cols(map->attrMap, rte->extraUpdatedCols);
 		else
 			return rte->extraUpdatedCols;
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 271ff2be8e..a3988b1175 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3481,7 +3481,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	/*
 	 * Convert the tuple, if necessary.
 	 */
-	map = partrel->ri_RootToPartitionMap;
+	map = ExecGetRootToChildMap(partrel, estate);
 	if (map != NULL)
 	{
 		TupleTableSlot *new_slot = partrel->ri_PartitionTupleSlot;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e48a3f589a..f9efe6c4c6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -2193,7 +2193,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 	remoteslot_part = partrelinfo->ri_PartitionTupleSlot;
 	if (remoteslot_part == NULL)
 		remoteslot_part = table_slot_create(partrel, &estate->es_tupleTable);
-	map = partrelinfo->ri_RootToPartitionMap;
+	map = ExecGetRootToChildMap(partrelinfo, estate);
 	if (map != NULL)
 	{
 		attrmap = map->attrMap;
@@ -2353,7 +2353,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata,
 					if (remoteslot_part == NULL)
 						remoteslot_part = table_slot_create(partrel_new,
 															&estate->es_tupleTable);
-					map = partrelinfo_new->ri_RootToPartitionMap;
+					map = ExecGetRootToChildMap(partrelinfo_new, estate);
 					if (map != NULL)
 					{
 						remoteslot_part = execute_attr_map_slot(map->attrMap,
diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h
index a37dafc666..97091fbed4 100644
--- a/src/include/access/tupconvert.h
+++ b/src/include/access/tupconvert.h
@@ -39,6 +39,9 @@ extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc,
 
 extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc,
 												  TupleDesc outdesc);
+extern TupleConversionMap *convert_tuples_by_name_attrmap(TupleDesc indesc,
+							   TupleDesc outdesc,
+							   AttrMap *attrMap);
 
 extern HeapTuple execute_attr_map_tuple(HeapTuple tuple, TupleConversionMap *map);
 extern TupleTableSlot *execute_attr_map_slot(AttrMap *attrMap,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c9a5e5fb68..32bbbc5927 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,6 +603,7 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
+extern TupleConversionMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate);
 
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 369de42caf..e01fe5646c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -546,12 +546,15 @@ typedef struct ResultRelInfo
 	 * mentioned in the query is an inherited table, nor when tuple routing is
 	 * not needed.
 	 *
-	 * RootToPartitionMap and PartitionTupleSlot, initialized by
-	 * ExecInitRoutingInfo, are non-NULL if partition has a different tuple
-	 * format than the root table.
+	 * RootToPartitionMap and PartitionTupleSlot are non-NULL if partition has
+	 * a different tuple format than the root table.
+	 *
+	 * Note: Despite the naming, RootToPartitionMap can also be used for
+	 * regular inheritance child relations.
 	 */
 	struct ResultRelInfo *ri_RootResultRelInfo;
 	TupleConversionMap *ri_RootToPartitionMap;
+	bool		ri_RootToPartitionMapValid;
 	TupleTableSlot *ri_PartitionTupleSlot;
 
 	/*
-- 
2.35.3



  [application/octet-stream] v31-0002-Rework-query-relation-permission-checking.patch (132.8K, ../../CA+HiwqGz_CYVpSv47VbFOxpzPKW63MRajRcwH-a9Qv_EEog8JA@mail.gmail.com/3-v31-0002-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From c59cf0f49e7b406dc6e685fd67b895dfc696ae26 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v31 2/2] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  17 +-
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/commands/copy.c                   |  23 ++-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/view.c                   |  33 ++-
 src/backend/executor/execMain.c               | 123 ++++++-----
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execUtils.c              | 146 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  73 +++++--
 src/backend/optimizer/plan/subselect.c        |   9 +-
 src/backend/optimizer/prep/prepjointree.c     |  19 +-
 src/backend/optimizer/util/inherit.c          | 173 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  68 ++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 178 +++++++++-------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   6 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 193 +++++++++---------
 src/backend/rewrite/rewriteManip.c            |  34 +++
 src/backend/rewrite/rowsecurity.c             |  25 ++-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   1 +
 src/include/nodes/parsenodes.h                |  94 ++++++---
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   2 +
 src/include/optimizer/inherit.h               |   2 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   3 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 src/test/regress/expected/rules.out           |  14 ++
 src/test/regress/sql/rules.sql                |  15 ++
 src/tools/pgindent/typedefs.list              |   2 +
 46 files changed, 968 insertions(+), 533 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 20c7b1ad05..1ceac2e0cf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -657,8 +658,8 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to lack
+	 * of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
@@ -1809,7 +1810,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = get_rel_all_updated_cols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -2650,7 +2652,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
 	userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
 
@@ -3975,11 +3977,8 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	/* Identify which user to do the remote access as. */
+	userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..02bc458238 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rteperminfos)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..d8efb915de 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..9e292271b7 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rteperminfos,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..b8bd78d358 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -150,15 +151,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
 		foreach(cur, attnums)
 		{
-			int			attno = lfirst_int(cur) -
-			FirstLowInvalidHeapAttributeNumber;
+			int			attno;
+			Bitmapset **bms;
 
-			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
-			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+			attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
+			bms = is_from ? &perminfo->insertedCols : &perminfo->selectedCols;
+
+			*bms = bms_add_member(*bms, attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +175,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 504afcb811..0371f51220 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rteperminfos = cstate->rteperminfos;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rteperminfos, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rteperminfos = pstate->p_rteperminfos;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..8e3c1efae4 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
+	RTEPermissionInfo *rte_perminfo1;
 	ParseState *pstate;
+	ListCell   *lc;
 
 	/*
 	 * Make a copy of the given parsetree.  It's not so much that we don't
@@ -405,15 +407,38 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   makeAlias("old", NIL),
 										   false, false);
 	rt_entry1 = nsitem->p_rte;
+	rte_perminfo1 = nsitem->p_perminfo;
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("new", NIL),
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
+	/*
+	 * Add only the "old" RTEPermissionInfo at the head of view query's list
+	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
+	 * query on the view, ApplyRetrieveRule() will transfer the view
+	 * relation's permission details into this RTEPermissionInfo.  That's
+	 * needed because the view's RTE itself will be transposed into a subquery
+	 * RTE that can't carry the permission details; see the code stanza toward
+	 * the end of ApplyRetrieveRule() for how that's done.
+	 */
+	viewParse->rteperminfos = lcons(rte_perminfo1, viewParse->rteperminfos);
+	foreach(lc, viewParse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += 1;
+	}
+
+	/*
+	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is a
+	 * bit of a hack given that all the non-child RTE_RELATION entries really
+	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
+	 * go away anyway in the very near future.
+	 */
+	rt_entry2->perminfoindex = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2d68aafc69..9f8392ac31 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -55,6 +55,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -75,8 +76,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -91,10 +92,10 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
-									  Bitmapset *modifiedCols,
-									  AclMode requiredPerms);
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
+										 Bitmapset *modifiedCols,
+										 AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
 static char *ExecBuildSlotValueDescription(Oid reloid,
 										   TupleTableSlot *slot,
@@ -603,8 +604,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -614,73 +615,65 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rteperminfos,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rteperminfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV,
+							   get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rteperminfos,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down
+	 * from there.  But for now, no need for the extra clutter.
 	 */
-	userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	userid = OidIsValid(perminfo->checkAsUser) ?
+		perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -714,14 +707,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -746,29 +739,31 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->insertedCols,
-																	  ACL_INSERT))
+		if (remainingPerms & ACL_INSERT &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->insertedCols,
+										  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->updatedCols,
-																	  ACL_UPDATE))
+		if (remainingPerms & ACL_UPDATE &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->updatedCols,
+										  ACL_UPDATE))
 			return false;
 	}
 	return true;
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
-						  AclMode requiredPerms)
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+							 AclMode requiredPerms)
 {
 	int			col = -1;
 
@@ -822,17 +817,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->permInfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -865,9 +857,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->permInfos, true);
+	estate->es_rteperminfos = plannedstmt->permInfos;
 
 	/*
 	 * initialize the node's execution state
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 917079a034..6f3f014cca 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -187,6 +187,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->planTree = plan;
 	pstmt->partPruneInfos = estate->es_part_prune_infos;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->permInfos = estate->es_rteperminfos;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 62c8f1688f..7bb3df324c 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -67,6 +68,7 @@
 
 static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, int varno, TupleDesc tupdesc);
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
+static inline RTEPermissionInfo *GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate);
 
 
 /* ----------------------------------------------------------------
@@ -1297,72 +1299,48 @@ ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate)
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (map != NULL)
-			return execute_attr_map_cols(map->attrMap, rte->insertedCols);
-		else
-			return rte->insertedCols;
-	}
-	else
-	{
-		/*
-		 * The relation isn't in the range table and it isn't a partition
-		 * routing target.  This ResultRelInfo must've been created only for
-		 * firing triggers and the relation is not being inserted into.  (See
-		 * ExecGetTriggerResultRel.)
-		 */
-		return NULL;
+		if (map)
+			return execute_attr_map_cols(map->attrMap, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (map != NULL)
-			return execute_attr_map_cols(map->attrMap, rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map->attrMap, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
@@ -1391,3 +1369,83 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 	return bms_union(ExecGetUpdatedCols(relinfo, estate),
 					 ExecGetExtraUpdatedCols(relinfo, estate));
 }
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Looks up RTEPermissionInfo for ExecGet*Cols() routines
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		/*
+		 * For inheritance child result relations (a partition routing target
+		 * of an INSERT or a child UPDATE target), this returns the root
+		 * parent's RTE to fetch the RTEPermissionInfo because that's the only
+		 * one that has one assigned.
+		 */
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	}
+	else if (relinfo->ri_RangeTableIndex != 0)
+	{
+		/*
+		 * Non-child result relation should have their own RTEPermissionInfo.
+		 */
+		rti = relinfo->ri_RangeTableIndex;
+	}
+	else
+	{
+		/*
+		 * The relation isn't in the range table and it isn't a partition
+		 * routing target.  This ResultRelInfo must've been created only for
+		 * firing triggers and the relation is not being inserted into.  (See
+		 * ExecGetTriggerResultRel.)
+		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = ExecGetRTEPermissionInfo(rte, estate);
+	}
+
+	return perminfo;
+}
+
+/*
+ * ExecGetRTEPermissionInfo
+ *		Returns the RTEPermissionInfo contained in estate->es_rteperminfos
+ *		pointed to by the RTE
+ */
+RTEPermissionInfo *
+ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate)
+{
+	Assert(estate->es_rteperminfos != NIL);
+	return GetRTEPermissionInfo(estate->es_rteperminfos, rte);
+}
+
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+Oid
+ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relInfo, estate);
+
+	/* XXX - maybe ok to return GetUserId() in this case? */
+	if (perminfo == NULL)
+		elog(ERROR, "no RTEPermissionInfo found for result relation with OID %u",
+			 RelationGetRelid(relInfo->ri_RelationDesc));
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8f150e9a2e..59b0fdeb62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -507,6 +507,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -560,11 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT64_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b01f55fb4f..1161671fa4 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -478,6 +478,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -541,11 +542,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a96d316dca..7c046de471 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrteperminfos = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrteperminfos == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -523,6 +526,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->containsInitialPruning = glob->containsInitialPruning;
 	result->rtable = glob->finalrtable;
 	result->minLockRelids = glob->minLockRelids;
+	result->permInfos = glob->finalrteperminfos;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6268,6 +6272,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rteperminfos, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6395,6 +6400,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	AddRTEPermissionInfo(&query->rteperminfos, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 5820f26fdb..217c25f170 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -78,6 +79,13 @@ typedef struct
 	int			newvarno;
 } fix_windowagg_cond_context;
 
+/* Context info for flatten_rtes_walker() */
+typedef struct
+{
+	PlannerGlobal *glob;
+	Query	   *query;
+} flatten_rtes_walker_context;
+
 /*
  * Selecting the best alternative in an AlternativeSubPlan expression requires
  * estimating how many times that expression will be evaluated.  For an
@@ -113,8 +121,9 @@ typedef struct
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
-static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
+static bool flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt);
+static void add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
+								   RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 										  IndexOnlyScan *plan,
@@ -426,6 +435,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rteperminfos.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -446,7 +458,7 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(glob, root->parse->rteperminfos, rte);
 	}
 
 	/*
@@ -513,18 +525,21 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 /*
  * Extract RangeTblEntries from a subquery that was never planned at all
  */
+
 static void
 flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 {
+	flatten_rtes_walker_context cxt = {glob, rte->subquery};
+
 	/* Use query_tree_walker to find all RTEs in the parse tree */
 	(void) query_tree_walker(rte->subquery,
 							 flatten_rtes_walker,
-							 (void *) glob,
+							 (void *) &cxt,
 							 QTW_EXAMINE_RTES_BEFORE);
 }
 
 static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
+flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt)
 {
 	if (node == NULL)
 		return false;
@@ -534,33 +549,38 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(cxt->glob, cxt->query->rteperminfos, rte);
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into subselects */
+		/*
+		 * Recurse into subselects.  Must update cxt->query to this query so
+		 * that the rtable and rteperminfos correspond with each other.
+		 */
+		cxt->query = (Query *) node;
 		return query_tree_walker((Query *) node,
 								 flatten_rtes_walker,
-								 (void *) glob,
+								 (void *) cxt,
 								 QTW_EXAMINE_RTES_BEFORE);
 	}
 	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+								  (void *) cxt);
 }
 
 /*
- * Add (a copy of) the given RTE to the final rangetable
+ * Add (a copy of) the given RTE to the final rangetable and also the
+ * corresponding RTEPermissionInfo, if any, to final rteperminfos.
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo.
  */
 static void
-add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
+add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
+					   RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
 
@@ -598,6 +618,29 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 */
 	if (newrte->rtekind == RTE_RELATION)
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+	/*
+	 * Add a copy of the RTEPermissionInfo, if any, corresponding to this RTE
+	 * to the flattened global list.
+	 */
+	if (rte->perminfoindex > 0)
+	{
+		RTEPermissionInfo *perminfo;
+		RTEPermissionInfo *newperminfo;
+
+		/* Get the existing one from this query's rteperminfos. */
+		perminfo = GetRTEPermissionInfo(rteperminfos, newrte);
+
+		/*
+		 * Add a new one to finalrteperminfos and copy the contents of the
+		 * existing one into it.  Note that AddRTEPermissionInfo() also
+		 * updates newrte->perminfoindex to point to newperminfo in
+		 * finalrteperminfos.
+		 */
+		newrte->perminfoindex = 0;	/* expected by AddRTEPermissionInfo() */
+		newperminfo = AddRTEPermissionInfo(&glob->finalrteperminfos, newrte);
+		memcpy(newperminfo, perminfo, sizeof(RTEPermissionInfo));
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..844971dba7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,8 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
-	/* Now we can attach the modified subquery rtable to the parent */
-	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	/*
+	 * Now we can attach the modified subquery rtable to the parent. This also
+	 * adds subquery's RTEPermissionInfos into the upper query.
+	 */
+	parse->rtable = CombineRangeTables(parse->rtable, subselect->rtable,
+									   subselect->rteperminfos,
+									   &parse->rteperminfos);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 2ea3ca734e..3eb006e1ad 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1209,8 +1202,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
 	 * code on the subquery ones too.)
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtable = list_concat(parse->rtable, subquery->rtable);
+	parse->rtable = CombineRangeTables(parse->rtable, subquery->rtable,
+									   subquery->rteperminfos,
+									   &parse->rteperminfos);
 
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
@@ -1347,8 +1344,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 
 	/*
 	 * Append child RTEs to parent rtable.
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->rtable = CombineRangeTables(root->parse->rtable, rtable,
+											 subquery->rteperminfos,
+											 &root->parse->rteperminfos);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..645b7310ab 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -47,6 +49,10 @@ static void expand_single_inheritance_child(PlannerInfo *root,
 											Index *childRTindex_p);
 static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 									  List *translated_vars);
+static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
+												 RelOptInfo *rel,
+												 RelOptInfo *top_parent_rel,
+												 Bitmapset *top_parent_cols);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
 									  RangeTblEntry *rte, Index rti);
 
@@ -131,6 +137,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = GetRTEPermissionInfo(root->parse->rteperminfos, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +151,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +317,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +337,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +414,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset  *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +473,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,9 +496,16 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
+	/*
+	 * No permission checking for the child RTE unless it's the parent
+	 * relation in its child role, which only applies to traditional
+	 * inheritance.
+	 */
+	if (childOID != parentOID)
+		childrte->perminfoindex = 0;
+
 	/* Link not-yet-fully-filled child RTE into data structures */
 	parse->rtable = lappend(parse->rtable, childrte);
 	childRTindex = list_length(parse->rtable);
@@ -539,33 +566,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -648,6 +654,54 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 }
 
+/*
+ * get_rel_all_updated_cols
+ * 		Returns the set of columns of a given "simple" relation that are
+ * 		updated by this query.
+ */
+Bitmapset *
+get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index		relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset  *updatedCols,
+			   *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+	Assert(IS_SIMPLE_REL(rel));
+
+	/*
+	 * We obtain updatedCols and extraUpdatedCols for the query's result
+	 * relation.  Then, if necessary, we map it to the column numbers of the
+	 * relation for which they were requested.
+	 */
+	relid = root->parse->resultRelation;
+	rte = planner_rt_fetch(relid, root);
+	perminfo = GetRTEPermissionInfo(root->parse->rteperminfos, rte);
+
+	updatedCols = perminfo->updatedCols;
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	/*
+	 * For "other" rels, we must look up the root parent relation mentioned in
+	 * the query, and translate the column numbers.
+	 */
+	if (rel->relid != relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, relid);
+
+		Assert(IS_OTHER_REL(rel));
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  extraUpdatedCols);
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
+
 /*
  * translate_col_privs
  *	  Translate a bitmapset representing per-column privileges from the
@@ -866,3 +920,40 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset  *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7b4434e7f..23d00a74da 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -223,7 +224,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though only
+		 * the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo;
+
+			perminfo = GetRTEPermissionInfo(root->parse->rteperminfos, rte);
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..6f870ffd8b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rteperminfos;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,17 +596,19 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
-	 * The SELECT's joinlist is not affected however.  We must do this before
-	 * adding the target table to the INSERT's rtable.
+	 * INSERT/SELECT, arrange to pass the rangetable/rteperminfos/namespace
+	 * down to the SELECT.  This can only happen if we are inside a CREATE
+	 * RULE, and in that case we want the rule's OLD and NEW rtable entries to
+	 * appear as part of the SELECT's rtable, not as outer references for it.
+	 * (Kluge!) The SELECT's joinlist is not affected however.  We must do
+	 * this before adding the target table to the INSERT's rtable.
 	 */
 	if (isGeneralSelect)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rteperminfos = pstate->p_rteperminfos;
+		pstate->p_rteperminfos = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rteperminfos = sub_rteperminfos;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = GetRTEPermissionInfo(qry->rteperminfos, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo = GetRTEPermissionInfo(qry->rteperminfos, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 62c2ff69f0..3844f2b45f 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -215,6 +215,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -287,7 +288,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -346,7 +347,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -360,8 +361,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4665f0b2b7..ca3e8b36f5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1037,11 +1037,15 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo;
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo = GetRTEPermissionInfo(pstate->p_rteperminfos, rte);
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
-										   col - FirstLowInvalidHeapAttributeNumber);
+		perminfo->selectedCols =
+			bms_add_member(perminfo->selectedCols,
+						   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
@@ -1251,10 +1255,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1290,6 +1297,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1433,6 +1441,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1469,7 +1478,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1478,12 +1487,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1497,7 +1502,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1534,6 +1539,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1557,7 +1563,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1566,12 +1572,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = AddRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1585,7 +1587,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1659,21 +1661,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1990,20 +1986,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2015,7 +2004,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2082,20 +2071,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2170,19 +2152,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2267,19 +2243,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2294,6 +2264,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2417,19 +2388,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2543,16 +2508,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * AddRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2564,7 +2526,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3189,6 +3151,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3206,7 +3169,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3855,3 +3821,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * AddRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+AddRTEPermissionInfo(List **rteperminfos, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rteperminfos = lappend(*rteperminfos, perminfo);
+
+	/* Note its index (1-based!) */
+	rte->perminfoindex = list_length(*rteperminfos);
+
+	return perminfo;
+}
+
+/*
+ * GetRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+GetRTEPermissionInfo(List *rteperminfos, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	if (rte->perminfoindex == 0 ||
+		rte->perminfoindex > list_length(rteperminfos))
+		elog(ERROR, "invalid perminfoindex %d in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = list_nth_node(RTEPermissionInfo, rteperminfos,
+							 rte->perminfoindex - 1);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match provided RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 8e0d6fd01f..56d64c8851 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 36791d8817..342a179133 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3023,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3081,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rteperminfos = pstate->p_rteperminfos;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3123,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index f9efe6c4c6..b97869e8b2 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	AddRTEPermissionInfo(&estate->es_rteperminfos, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rteperminfos, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index db45d8a08b..11947b9cee 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -797,14 +797,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -831,18 +831,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rteperminfos)
+	{
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..b7c08a8e7a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *action_rteperminfos;
 
 	context.for_execute = true;
 
@@ -395,32 +396,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its rteperminfos must be preserved so that the executor will
+	 * do the correct permissions checks on the relations referenced in it.
+	 * This allows us to check that the caller has, say, insert-permission on
+	 * a view, when the view is not semantically referenced at all in the
+	 * resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rteperminfos,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	action_rteperminfos = sub_action->rteperminfos;
+	sub_action->rteperminfos = copyObject(parsetree->rteperminfos);
+	sub_action->rtable = CombineRangeTables(copyObject(parsetree->rtable),
+											sub_action->rtable,
+											action_rteperminfos,
+											&sub_action->rteperminfos);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1628,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1657,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1743,6 +1750,8 @@ ApplyRetrieveRule(Query *parsetree,
 	Query	   *rule_action;
 	RangeTblEntry *rte,
 			   *subrte;
+	RTEPermissionInfo *perminfo,
+			   *sub_perminfo;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1792,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1855,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1872,6 +1863,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	perminfo = GetRTEPermissionInfo(parsetree->rteperminfos, rte);
 
 	rte->rtekind = RTE_SUBQUERY;
 	rte->subquery = rule_action;
@@ -1881,6 +1873,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;		/* no permission checking for this RTE */
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
@@ -1889,19 +1882,12 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	sub_perminfo = GetRTEPermissionInfo(rule_action->rteperminfos, subrte);
+	sub_perminfo->requiredPerms = perminfo->requiredPerms;
+	sub_perminfo->checkAsUser = perminfo->checkAsUser;
+	sub_perminfo->selectedCols = perminfo->selectedCols;
+	sub_perminfo->insertedCols = perminfo->insertedCols;
+	sub_perminfo->updatedCols = perminfo->updatedCols;
 
 	return parsetree;
 }
@@ -1931,8 +1917,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = GetRTEPermissionInfo(qry->rteperminfos, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3063,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3202,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = GetRTEPermissionInfo(viewquery->rteperminfos, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3274,68 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * rteperminfos so that the executor still performs appropriate permissions
+	 * checks for the query caller's use of the view.
+	 */
+	view_perminfo = GetRTEPermissionInfo(parsetree->rteperminfos, view_rte);
+
+	/*
+	 * Disregard the perminfo in viewquery->rteperminfos that the base_rte
+	 * would currently be pointing at, because we'd like it to point now
+	 * to a new one that will be filled below.  Must set perminfoindex to
+	 * 0 to not trip over the Assert in AddRTEPermissionInfo().
 	 */
+	new_rte->perminfoindex = 0;
+	new_perminfo = AddRTEPermissionInfo(&parsetree->rteperminfos, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Initially, new_perminfo (base_perminfo) contains selectedCols permission
+	 * check bits for all base-rel columns referenced by the view, but since
+	 * the view is a SELECT query its insertedCols/updatedCols is empty.  We
+	 * set insertedCols and updatedCols to include all the columns the outer
+	 * query is trying to modify, adjusting the column numbers as needed.  But
+	 * we leave selectedCols as-is, so the view owner must have read permission
+	 * for all columns used in the view definition, even if some of them are
+	 * not read by the outer query.  We could try to limit selectedCols to only
+	 * columns used in the transformed query, but that does not correspond to
+	 * what happens in ordinary SELECT usage of a view: all referenced columns
+	 * must have read permission, even if optimization finds that some of them
+	 * can be discarded during query transformation.  The flattening we're
+	 * doing here is an optional optimization, too.  (If you are unpersuaded
+	 * and want to change this, note that applying adjust_view_column_set to
+	 * view_perminfo->selectedCols is clearly *not* the right answer, since
+	 * that neglects base-rel columns used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
+
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3439,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3450,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
+		/* Ignore the RTEPermissionInfo that would've been added. */
+		new_exclRte->perminfoindex = 0;
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3725,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3737,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = GetRTEPermissionInfo(parsetree->rteperminfos, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3824,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..04bec1037e 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1531,3 +1531,37 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+/*
+ * CombineRangeTables
+ * 		Adds the RTEs of 'rtable2' into 'rtable1'
+ *
+ * This also adds the RTEPermissionInfos of 'rteperminfos2' (belonging to the
+ * RTEs in 'rtable2') into *rteperminfos1 and also updates perminfoindex of the
+ * RTEs in 'rtable2' to now point to the perminfos' indexes in *rteperminfos1.
+ *
+ * Note that this changes both 'rtable1' and 'rteperminfos1' destructively, so
+ * the caller should have better passed safe-to-modify copies.
+ */
+List *
+CombineRangeTables(List *rtable1, List *rtable2,
+				   List *rteperminfos2, List **rteperminfos1)
+{
+	ListCell   *l;
+	int			offset = list_length(*rteperminfos1);
+
+	if (offset > 0)
+	{
+		foreach(l, rtable2)
+		{
+			RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += offset;
+		}
+	}
+
+	*rteperminfos1 = list_concat(*rteperminfos1, rteperminfos2);
+
+	return list_concat(rtable1, rtable2);
+}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index f49cfb6cc6..7c964488b9 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,21 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = GetRTEPermissionInfo(root->rteperminfos, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	user_id = OidIsValid(perminfo->checkAsUser) ?
+		perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +203,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +250,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +293,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +349,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +378,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +481,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc07157037..29f29d664b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..c3feacb4f8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in
+		 * case it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..c5e5875eb8 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rteperminfos;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 32bbbc5927..3c05fa3e1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -199,7 +199,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+								 List *rteperminfos, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -579,6 +580,7 @@ exec_rt_fetch(Index rti, EState *estate)
 }
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
+extern RTEPermissionInfo *ExecGetRTEPermissionInfo(RangeTblEntry *rte, EState *estate);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 								   Index rti);
 
@@ -605,6 +607,7 @@ extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relIn
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
 extern TupleConversionMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate);
 
+extern Oid	ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate);
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e01fe5646c..b7950e83b4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -616,6 +616,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rteperminfos; /* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	List		*es_part_prune_infos;	/* PlannedStmt.partPruneInfos */
 	List		*es_part_prune_results; /* QueryDesc.part_prune_results */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6112cd85c8..7fea34144f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -154,6 +154,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
+								 * rtable entries having perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -968,37 +970,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1054,11 +1025,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the containing struct's list of same; 0 if permissions need
+	 * not be checked for this RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1178,14 +1154,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns they need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 36abe4cf9e..cd169510f9 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrteperminfos;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0cab6958d7..bbb4de75cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -83,6 +83,8 @@ typedef struct PlannedStmt
 								 * indexes of range table entries of the leaf
 								 * partitions scanned by prunable subplans;
 								 * see AcquireExecutorLocks() */
+	List	   *permInfos;		/* list of RTEPermissionInfo nodes for rtable
+								 * entries needing one */
 
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..8ebd42b757 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -20,6 +20,8 @@
 extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 									 RangeTblEntry *rte, Index rti);
 
+extern Bitmapset *get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel);
+
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..ea84684acc 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rteperminfos: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rteperminfos;	/* list of RTEPermissionInfo nodes for each
+								 * RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rteperminfos.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rteperminfos entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..c9c1d4cc8a 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,5 +120,9 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern RTEPermissionInfo *AddRTEPermissionInfo(List **rteperminfos,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *GetRTEPermissionInfo(List *rteperminfos,
+											   RangeTblEntry *rte);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..2d7421bedb 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -83,5 +83,8 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 									   ReplaceVarsNoMatchOption nomatch_option,
 									   int nomatch_varno,
 									   bool *outer_hasSubLinks);
+extern List *CombineRangeTables(List *rtable1, List *rtable2,
+								List *rteperminfos2,
+								List **rteperminfos1);
 
 #endif							/* REWRITEMANIP_H */
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..de1d3a4a94 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
 		allow = false;
 
 	if (allow)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 37c1c86473..e1cdaf9eb6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3584,6 +3584,18 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 RESET SESSION AUTHORIZATION;
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+ERROR:  permission denied for table ruletest_t3
+RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
  x 
 ---
@@ -3596,6 +3608,8 @@ SELECT * FROM ruletest_t2;
 (1 row)
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 DROP USER regress_rule_user1;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index bfb5f3b0bb..2f7cb8482a 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1309,11 +1309,26 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 
+RESET SESSION AUTHORIZATION;
+
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+
 RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
 SELECT * FROM ruletest_t2;
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 58daeca831..a2dfd5c9da 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2187,6 +2187,7 @@ RI_ConstraintInfo
 RI_QueryHashEntry
 RI_QueryKey
 RTEKind
+RTEPermissionInfo
 RWConflict
 RWConflictPoolHeader
 Range
@@ -3264,6 +3265,7 @@ fix_scan_expr_context
 fix_upper_expr_context
 fix_windowagg_cond_context
 flatten_join_alias_vars_context
+flatten_rtes_walker_context
 float4
 float4KEY
 float8
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-02 09:59  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2022-12-02 09:59 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hello,

On 2022-Dec-02, Amit Langote wrote:

> This sounds like a better idea than adding a new AttrMap, so done this
> way in the attached 0001.

Thanks for doing that!  I have pushed it, but I renamed
ri_RootToPartitionMap to ri_RootToChildMap and moved it to another spot
in ResultRelInfo, which allows to simplify the comments.

> I've also merged into 0002 the delta patch I had posted earlier to add
> a copy of RTEPermInfos into the flattened permInfos list instead of
> adding the Query's copy.

Great.  At this point I have no other comments, except that in both
parse_relation.c and rewriteManip.c you've chosen to add the new
functions at the bottom of each file, which is seldom a good choice.
I think in the case of CombineRangeTables it should be the very first
function in the file, before all the walker-type stuff; and for
Add/GetRTEPermissionInfo I would suggest that right below
addRangeTableEntryForENR might be a decent choice (need to fix the .h
files to match, of course.)

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/
"World domination is proceeding according to plan"        (Andrew Morton)





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-02 11:13  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-12-02 11:13 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Dec 2, 2022 at 7:00 PM Alvaro Herrera <[email protected]> wrote:
> On 2022-Dec-02, Amit Langote wrote:
> > This sounds like a better idea than adding a new AttrMap, so done this
> > way in the attached 0001.
>
> Thanks for doing that!  I have pushed it, but I renamed
> ri_RootToPartitionMap to ri_RootToChildMap and moved it to another spot
> in ResultRelInfo, which allows to simplify the comments.

Thanks.

> > I've also merged into 0002 the delta patch I had posted earlier to add
> > a copy of RTEPermInfos into the flattened permInfos list instead of
> > adding the Query's copy.
>
> Great.  At this point I have no other comments, except that in both
> parse_relation.c and rewriteManip.c you've chosen to add the new
> functions at the bottom of each file, which is seldom a good choice.
> I think in the case of CombineRangeTables it should be the very first
> function in the file, before all the walker-type stuff; and for
> Add/GetRTEPermissionInfo I would suggest that right below
> addRangeTableEntryForENR might be a decent choice (need to fix the .h
> files to match, of course.)

Okay, I've moved the functions and their .h declarations to the places
you suggest.  While at it, I also uncapitalized Add/Get, because
that's how the nearby functions in the header are named.

Thanks again for the review.  The patch looks much better than it did
3 weeks ago.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v32-0001-Rework-query-relation-permission-checking.patch (133.0K, ../../CA+HiwqG6K=YDX_c7+C3T=RQ7NPsJpUnFHagaWaVsV6qArfbGOA@mail.gmail.com/2-v32-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From 04daedcf0a67a130a5885c2f92ee08ceb3782cd6 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v32] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  17 +-
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/commands/copy.c                   |  23 ++-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/view.c                   |  33 ++-
 src/backend/executor/execMain.c               | 123 ++++++-----
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execUtils.c              | 146 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  73 +++++--
 src/backend/optimizer/plan/subselect.c        |   9 +-
 src/backend/optimizer/prep/prepjointree.c     |  19 +-
 src/backend/optimizer/util/inherit.c          | 173 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  68 ++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 178 +++++++++-------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   6 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 193 +++++++++---------
 src/backend/rewrite/rewriteManip.c            |  33 +++
 src/backend/rewrite/rowsecurity.c             |  25 ++-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   1 +
 src/include/nodes/parsenodes.h                |  94 ++++++---
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   2 +
 src/include/optimizer/inherit.h               |   2 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   3 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 src/test/regress/expected/rules.out           |  14 ++
 src/test/regress/sql/rules.sql                |  15 ++
 src/tools/pgindent/typedefs.list              |   2 +
 46 files changed, 967 insertions(+), 533 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 20c7b1ad05..1ceac2e0cf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -657,8 +658,8 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to lack
+	 * of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
@@ -1809,7 +1810,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = get_rel_all_updated_cols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -2650,7 +2652,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
 	userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
 
@@ -3975,11 +3977,8 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	/* Identify which user to do the remote access as. */
+	userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..02bc458238 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rteperminfos)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..d8efb915de 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..9e292271b7 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rteperminfos,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..b8bd78d358 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -150,15 +151,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
 		foreach(cur, attnums)
 		{
-			int			attno = lfirst_int(cur) -
-			FirstLowInvalidHeapAttributeNumber;
+			int			attno;
+			Bitmapset **bms;
 
-			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
-			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+			attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
+			bms = is_from ? &perminfo->insertedCols : &perminfo->selectedCols;
+
+			*bms = bms_add_member(*bms, attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +175,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 504afcb811..0371f51220 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rteperminfos = cstate->rteperminfos;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rteperminfos, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rteperminfos = pstate->p_rteperminfos;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..8e3c1efae4 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
+	RTEPermissionInfo *rte_perminfo1;
 	ParseState *pstate;
+	ListCell   *lc;
 
 	/*
 	 * Make a copy of the given parsetree.  It's not so much that we don't
@@ -405,15 +407,38 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   makeAlias("old", NIL),
 										   false, false);
 	rt_entry1 = nsitem->p_rte;
+	rte_perminfo1 = nsitem->p_perminfo;
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("new", NIL),
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
+	/*
+	 * Add only the "old" RTEPermissionInfo at the head of view query's list
+	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
+	 * query on the view, ApplyRetrieveRule() will transfer the view
+	 * relation's permission details into this RTEPermissionInfo.  That's
+	 * needed because the view's RTE itself will be transposed into a subquery
+	 * RTE that can't carry the permission details; see the code stanza toward
+	 * the end of ApplyRetrieveRule() for how that's done.
+	 */
+	viewParse->rteperminfos = lcons(rte_perminfo1, viewParse->rteperminfos);
+	foreach(lc, viewParse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += 1;
+	}
+
+	/*
+	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is a
+	 * bit of a hack given that all the non-child RTE_RELATION entries really
+	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
+	 * go away anyway in the very near future.
+	 */
+	rt_entry2->perminfoindex = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d26499c214..442ac85854 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -55,6 +55,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -75,8 +76,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -91,10 +92,10 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
-									  Bitmapset *modifiedCols,
-									  AclMode requiredPerms);
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
+										 Bitmapset *modifiedCols,
+										 AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
 static char *ExecBuildSlotValueDescription(Oid reloid,
 										   TupleTableSlot *slot,
@@ -605,8 +606,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -616,73 +617,65 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rteperminfos,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rteperminfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV,
+							   get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rteperminfos,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down
+	 * from there.  But for now, no need for the extra clutter.
 	 */
-	userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	userid = OidIsValid(perminfo->checkAsUser) ?
+		perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -716,14 +709,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -748,29 +741,31 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->insertedCols,
-																	  ACL_INSERT))
+		if (remainingPerms & ACL_INSERT &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->insertedCols,
+										  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->updatedCols,
-																	  ACL_UPDATE))
+		if (remainingPerms & ACL_UPDATE &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->updatedCols,
+										  ACL_UPDATE))
 			return false;
 	}
 	return true;
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
-						  AclMode requiredPerms)
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+							 AclMode requiredPerms)
 {
 	int			col = -1;
 
@@ -824,17 +819,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->permInfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -867,9 +859,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->permInfos, true);
+	estate->es_rteperminfos = plannedstmt->permInfos;
 
 	/*
 	 * initialize the node's execution state
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 917079a034..6f3f014cca 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -187,6 +187,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->planTree = plan;
 	pstmt->partPruneInfos = estate->es_part_prune_infos;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->permInfos = estate->es_rteperminfos;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 044bf3f491..096058b296 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -67,6 +68,7 @@
 
 static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, int varno, TupleDesc tupdesc);
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
+static inline RTEPermissionInfo *GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate);
 
 
 /* ----------------------------------------------------------------
@@ -1297,72 +1299,48 @@ ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate)
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (map != NULL)
-			return execute_attr_map_cols(map->attrMap, rte->insertedCols);
-		else
-			return rte->insertedCols;
-	}
-	else
-	{
-		/*
-		 * The relation isn't in the range table and it isn't a partition
-		 * routing target.  This ResultRelInfo must've been created only for
-		 * firing triggers and the relation is not being inserted into.  (See
-		 * ExecGetTriggerResultRel.)
-		 */
-		return NULL;
+		if (map)
+			return execute_attr_map_cols(map->attrMap, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (map != NULL)
-			return execute_attr_map_cols(map->attrMap, rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map->attrMap, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
@@ -1391,3 +1369,83 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 	return bms_union(ExecGetUpdatedCols(relinfo, estate),
 					 ExecGetExtraUpdatedCols(relinfo, estate));
 }
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Looks up RTEPermissionInfo for ExecGet*Cols() routines
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		/*
+		 * For inheritance child result relations (a partition routing target
+		 * of an INSERT or a child UPDATE target), this returns the root
+		 * parent's RTE to fetch the RTEPermissionInfo because that's the only
+		 * one that has one assigned.
+		 */
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	}
+	else if (relinfo->ri_RangeTableIndex != 0)
+	{
+		/*
+		 * Non-child result relation should have their own RTEPermissionInfo.
+		 */
+		rti = relinfo->ri_RangeTableIndex;
+	}
+	else
+	{
+		/*
+		 * The relation isn't in the range table and it isn't a partition
+		 * routing target.  This ResultRelInfo must've been created only for
+		 * firing triggers and the relation is not being inserted into.  (See
+		 * ExecGetTriggerResultRel.)
+		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = ExecgetRTEPermissionInfo(rte, estate);
+	}
+
+	return perminfo;
+}
+
+/*
+ * ExecgetRTEPermissionInfo
+ *		Returns the RTEPermissionInfo contained in estate->es_rteperminfos
+ *		pointed to by the RTE
+ */
+RTEPermissionInfo *
+ExecgetRTEPermissionInfo(RangeTblEntry *rte, EState *estate)
+{
+	Assert(estate->es_rteperminfos != NIL);
+	return getRTEPermissionInfo(estate->es_rteperminfos, rte);
+}
+
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+Oid
+ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relInfo, estate);
+
+	/* XXX - maybe ok to return GetUserId() in this case? */
+	if (perminfo == NULL)
+		elog(ERROR, "no RTEPermissionInfo found for result relation with OID %u",
+			 RelationGetRelid(relInfo->ri_RelationDesc));
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8f150e9a2e..59b0fdeb62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -507,6 +507,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -560,11 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT64_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b01f55fb4f..1161671fa4 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -478,6 +478,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -541,11 +542,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a96d316dca..3cb1fb9334 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrteperminfos = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrteperminfos == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -523,6 +526,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->containsInitialPruning = glob->containsInitialPruning;
 	result->rtable = glob->finalrtable;
 	result->minLockRelids = glob->minLockRelids;
+	result->permInfos = glob->finalrteperminfos;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6268,6 +6272,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	addRTEPermissionInfo(&query->rteperminfos, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6395,6 +6400,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	addRTEPermissionInfo(&query->rteperminfos, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 5820f26fdb..76364cd34d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -78,6 +79,13 @@ typedef struct
 	int			newvarno;
 } fix_windowagg_cond_context;
 
+/* Context info for flatten_rtes_walker() */
+typedef struct
+{
+	PlannerGlobal *glob;
+	Query	   *query;
+} flatten_rtes_walker_context;
+
 /*
  * Selecting the best alternative in an AlternativeSubPlan expression requires
  * estimating how many times that expression will be evaluated.  For an
@@ -113,8 +121,9 @@ typedef struct
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
-static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
+static bool flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt);
+static void add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
+								   RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 										  IndexOnlyScan *plan,
@@ -426,6 +435,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rteperminfos.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -446,7 +458,7 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(glob, root->parse->rteperminfos, rte);
 	}
 
 	/*
@@ -513,18 +525,21 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 /*
  * Extract RangeTblEntries from a subquery that was never planned at all
  */
+
 static void
 flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 {
+	flatten_rtes_walker_context cxt = {glob, rte->subquery};
+
 	/* Use query_tree_walker to find all RTEs in the parse tree */
 	(void) query_tree_walker(rte->subquery,
 							 flatten_rtes_walker,
-							 (void *) glob,
+							 (void *) &cxt,
 							 QTW_EXAMINE_RTES_BEFORE);
 }
 
 static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
+flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt)
 {
 	if (node == NULL)
 		return false;
@@ -534,33 +549,38 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(cxt->glob, cxt->query->rteperminfos, rte);
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into subselects */
+		/*
+		 * Recurse into subselects.  Must update cxt->query to this query so
+		 * that the rtable and rteperminfos correspond with each other.
+		 */
+		cxt->query = (Query *) node;
 		return query_tree_walker((Query *) node,
 								 flatten_rtes_walker,
-								 (void *) glob,
+								 (void *) cxt,
 								 QTW_EXAMINE_RTES_BEFORE);
 	}
 	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+								  (void *) cxt);
 }
 
 /*
- * Add (a copy of) the given RTE to the final rangetable
+ * Add (a copy of) the given RTE to the final rangetable and also the
+ * corresponding RTEPermissionInfo, if any, to final rteperminfos.
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo.
  */
 static void
-add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
+add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
+					   RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
 
@@ -598,6 +618,29 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 */
 	if (newrte->rtekind == RTE_RELATION)
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+	/*
+	 * Add a copy of the RTEPermissionInfo, if any, corresponding to this RTE
+	 * to the flattened global list.
+	 */
+	if (rte->perminfoindex > 0)
+	{
+		RTEPermissionInfo *perminfo;
+		RTEPermissionInfo *newperminfo;
+
+		/* Get the existing one from this query's rteperminfos. */
+		perminfo = getRTEPermissionInfo(rteperminfos, newrte);
+
+		/*
+		 * Add a new one to finalrteperminfos and copy the contents of the
+		 * existing one into it.  Note that addRTEPermissionInfo() also
+		 * updates newrte->perminfoindex to point to newperminfo in
+		 * finalrteperminfos.
+		 */
+		newrte->perminfoindex = 0;	/* expected by addRTEPermissionInfo() */
+		newperminfo = addRTEPermissionInfo(&glob->finalrteperminfos, newrte);
+		memcpy(newperminfo, perminfo, sizeof(RTEPermissionInfo));
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..844971dba7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,8 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
-	/* Now we can attach the modified subquery rtable to the parent */
-	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	/*
+	 * Now we can attach the modified subquery rtable to the parent. This also
+	 * adds subquery's RTEPermissionInfos into the upper query.
+	 */
+	parse->rtable = CombineRangeTables(parse->rtable, subselect->rtable,
+									   subselect->rteperminfos,
+									   &parse->rteperminfos);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 2ea3ca734e..3eb006e1ad 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1209,8 +1202,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
 	 * code on the subquery ones too.)
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtable = list_concat(parse->rtable, subquery->rtable);
+	parse->rtable = CombineRangeTables(parse->rtable, subquery->rtable,
+									   subquery->rteperminfos,
+									   &parse->rteperminfos);
 
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
@@ -1347,8 +1344,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 
 	/*
 	 * Append child RTEs to parent rtable.
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->rtable = CombineRangeTables(root->parse->rtable, rtable,
+											 subquery->rteperminfos,
+											 &root->parse->rteperminfos);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..3d6f75baee 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -47,6 +49,10 @@ static void expand_single_inheritance_child(PlannerInfo *root,
 											Index *childRTindex_p);
 static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 									  List *translated_vars);
+static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
+												 RelOptInfo *rel,
+												 RelOptInfo *top_parent_rel,
+												 Bitmapset *top_parent_cols);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
 									  RangeTblEntry *rte, Index rti);
 
@@ -131,6 +137,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +151,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +317,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +337,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +414,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset  *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +473,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,9 +496,16 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
+	/*
+	 * No permission checking for the child RTE unless it's the parent
+	 * relation in its child role, which only applies to traditional
+	 * inheritance.
+	 */
+	if (childOID != parentOID)
+		childrte->perminfoindex = 0;
+
 	/* Link not-yet-fully-filled child RTE into data structures */
 	parse->rtable = lappend(parse->rtable, childrte);
 	childRTindex = list_length(parse->rtable);
@@ -539,33 +566,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -648,6 +654,54 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 }
 
+/*
+ * get_rel_all_updated_cols
+ * 		Returns the set of columns of a given "simple" relation that are
+ * 		updated by this query.
+ */
+Bitmapset *
+get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index		relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset  *updatedCols,
+			   *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+	Assert(IS_SIMPLE_REL(rel));
+
+	/*
+	 * We obtain updatedCols and extraUpdatedCols for the query's result
+	 * relation.  Then, if necessary, we map it to the column numbers of the
+	 * relation for which they were requested.
+	 */
+	relid = root->parse->resultRelation;
+	rte = planner_rt_fetch(relid, root);
+	perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+
+	updatedCols = perminfo->updatedCols;
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	/*
+	 * For "other" rels, we must look up the root parent relation mentioned in
+	 * the query, and translate the column numbers.
+	 */
+	if (rel->relid != relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, relid);
+
+		Assert(IS_OTHER_REL(rel));
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  extraUpdatedCols);
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
+
 /*
  * translate_col_privs
  *	  Translate a bitmapset representing per-column privileges from the
@@ -866,3 +920,40 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset  *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7b4434e7f..7085cf3c41 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -223,7 +224,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though only
+		 * the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo;
+
+			perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..2e593aed2b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rteperminfos;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,17 +596,19 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
-	 * The SELECT's joinlist is not affected however.  We must do this before
-	 * adding the target table to the INSERT's rtable.
+	 * INSERT/SELECT, arrange to pass the rangetable/rteperminfos/namespace
+	 * down to the SELECT.  This can only happen if we are inside a CREATE
+	 * RULE, and in that case we want the rule's OLD and NEW rtable entries to
+	 * appear as part of the SELECT's rtable, not as outer references for it.
+	 * (Kluge!) The SELECT's joinlist is not affected however.  We must do
+	 * this before adding the target table to the INSERT's rtable.
 	 */
 	if (isGeneralSelect)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rteperminfos = pstate->p_rteperminfos;
+		pstate->p_rteperminfos = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rteperminfos = sub_rteperminfos;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 62c2ff69f0..3844f2b45f 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -215,6 +215,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -287,7 +288,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -346,7 +347,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -360,8 +361,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4665f0b2b7..a3addb111d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1037,11 +1037,15 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo;
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo = getRTEPermissionInfo(pstate->p_rteperminfos, rte);
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
-										   col - FirstLowInvalidHeapAttributeNumber);
+		perminfo->selectedCols =
+			bms_add_member(perminfo->selectedCols,
+						   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
@@ -1251,10 +1255,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1290,6 +1297,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1433,6 +1441,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1469,7 +1478,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1478,12 +1487,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1497,7 +1502,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1534,6 +1539,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1557,7 +1563,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1566,12 +1572,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1585,7 +1587,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1659,21 +1661,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1990,20 +1986,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2015,7 +2004,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2082,20 +2071,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2170,19 +2152,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2267,19 +2243,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2294,6 +2264,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2417,19 +2388,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2543,16 +2508,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2564,7 +2526,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3189,6 +3151,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3206,7 +3169,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3855,3 +3821,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * addRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+addRTEPermissionInfo(List **rteperminfos, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rteperminfos = lappend(*rteperminfos, perminfo);
+
+	/* Note its index (1-based!) */
+	rte->perminfoindex = list_length(*rteperminfos);
+
+	return perminfo;
+}
+
+/*
+ * getRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+getRTEPermissionInfo(List *rteperminfos, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	if (rte->perminfoindex == 0 ||
+		rte->perminfoindex > list_length(rteperminfos))
+		elog(ERROR, "invalid perminfoindex %d in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = list_nth_node(RTEPermissionInfo, rteperminfos,
+							 rte->perminfoindex - 1);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match provided RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 8e0d6fd01f..56d64c8851 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 36791d8817..342a179133 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3023,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3081,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rteperminfos = pstate->p_rteperminfos;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3123,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index f9efe6c4c6..96772e4d73 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	addRTEPermissionInfo(&estate->es_rteperminfos, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rteperminfos, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index db45d8a08b..11947b9cee 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -797,14 +797,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -831,18 +831,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rteperminfos)
+	{
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fb0c687bd8..88f382246f 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *action_rteperminfos;
 
 	context.for_execute = true;
 
@@ -395,32 +396,35 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its rteperminfos must be preserved so that the executor will
+	 * do the correct permissions checks on the relations referenced in it.
+	 * This allows us to check that the caller has, say, insert-permission on
+	 * a view, when the view is not semantically referenced at all in the
+	 * resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * NOTE: because planner will destructively alter rtable and rteperminfos,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	action_rteperminfos = sub_action->rteperminfos;
+	sub_action->rteperminfos = copyObject(parsetree->rteperminfos);
+	sub_action->rtable = CombineRangeTables(copyObject(parsetree->rtable),
+											sub_action->rtable,
+											action_rteperminfos,
+											&sub_action->rteperminfos);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1624,10 +1628,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1650,7 +1657,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1743,6 +1750,8 @@ ApplyRetrieveRule(Query *parsetree,
 	Query	   *rule_action;
 	RangeTblEntry *rte,
 			   *subrte;
+	RTEPermissionInfo *perminfo,
+			   *sub_perminfo;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1783,18 +1792,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1858,12 +1855,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1872,6 +1863,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rte);
 
 	rte->rtekind = RTE_SUBQUERY;
 	rte->subquery = rule_action;
@@ -1881,6 +1873,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;		/* no permission checking for this RTE */
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
@@ -1889,19 +1882,12 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	sub_perminfo = getRTEPermissionInfo(rule_action->rteperminfos, subrte);
+	sub_perminfo->requiredPerms = perminfo->requiredPerms;
+	sub_perminfo->checkAsUser = perminfo->checkAsUser;
+	sub_perminfo->selectedCols = perminfo->selectedCols;
+	sub_perminfo->insertedCols = perminfo->insertedCols;
+	sub_perminfo->updatedCols = perminfo->updatedCols;
 
 	return parsetree;
 }
@@ -1931,8 +1917,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3073,6 +3063,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3209,6 +3202,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = getRTEPermissionInfo(viewquery->rteperminfos, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3280,57 +3274,68 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * rteperminfos so that the executor still performs appropriate permissions
+	 * checks for the query caller's use of the view.
+	 */
+	view_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, view_rte);
+
+	/*
+	 * Disregard the perminfo in viewquery->rteperminfos that the base_rte
+	 * would currently be pointing at, because we'd like it to point now
+	 * to a new one that will be filled below.  Must set perminfoindex to
+	 * 0 to not trip over the Assert in addRTEPermissionInfo().
 	 */
+	new_rte->perminfoindex = 0;
+	new_perminfo = addRTEPermissionInfo(&parsetree->rteperminfos, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Initially, new_perminfo (base_perminfo) contains selectedCols permission
+	 * check bits for all base-rel columns referenced by the view, but since
+	 * the view is a SELECT query its insertedCols/updatedCols is empty.  We
+	 * set insertedCols and updatedCols to include all the columns the outer
+	 * query is trying to modify, adjusting the column numbers as needed.  But
+	 * we leave selectedCols as-is, so the view owner must have read permission
+	 * for all columns used in the view definition, even if some of them are
+	 * not read by the outer query.  We could try to limit selectedCols to only
+	 * columns used in the transformed query, but that does not correspond to
+	 * what happens in ordinary SELECT usage of a view: all referenced columns
+	 * must have read permission, even if optimization finds that some of them
+	 * can be discarded during query transformation.  The flattening we're
+	 * doing here is an optional optimization, too.  (If you are unpersuaded
+	 * and want to change this, note that applying adjust_view_column_set to
+	 * view_perminfo->selectedCols is clearly *not* the right answer, since
+	 * that neglects base-rel columns used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
+
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3434,7 +3439,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3445,8 +3450,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
+		/* Ignore the RTEPermissionInfo that would've been added. */
+		new_exclRte->perminfoindex = 0;
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3720,6 +3725,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		List	   *product_queries;
@@ -3731,6 +3737,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3817,7 +3824,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..bf8bbbacc1 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -316,6 +316,39 @@ contains_multiexpr_param(Node *node, void *context)
 	return expression_tree_walker(node, contains_multiexpr_param, context);
 }
 
+/*
+ * CombineRangeTables
+ * 		Adds the RTEs of 'rtable2' into 'rtable1'
+ *
+ * This also adds the RTEPermissionInfos of 'rteperminfos2' (belonging to the
+ * RTEs in 'rtable2') into *rteperminfos1 and also updates perminfoindex of the
+ * RTEs in 'rtable2' to now point to the perminfos' indexes in *rteperminfos1.
+ *
+ * Note that this changes both 'rtable1' and 'rteperminfos1' destructively, so
+ * the caller should have better passed safe-to-modify copies.
+ */
+List *
+CombineRangeTables(List *rtable1, List *rtable2,
+				   List *rteperminfos2, List **rteperminfos1)
+{
+	ListCell   *l;
+	int			offset = list_length(*rteperminfos1);
+
+	if (offset > 0)
+	{
+		foreach(l, rtable2)
+		{
+			RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += offset;
+		}
+	}
+
+	*rteperminfos1 = list_concat(*rteperminfos1, rteperminfos2);
+
+	return list_concat(rtable1, rtable2);
+}
 
 /*
  * OffsetVarNodes - adjust Vars when appending one query's RT to another
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index f49cfb6cc6..f03b36a6e4 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,21 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = getRTEPermissionInfo(root->rteperminfos, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	user_id = OidIsValid(perminfo->checkAsUser) ?
+		perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +203,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +250,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +293,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +349,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +378,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +481,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc07157037..29f29d664b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..c3feacb4f8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in
+		 * case it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..c5e5875eb8 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rteperminfos;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 32bbbc5927..98ee6876b6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -199,7 +199,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+								 List *rteperminfos, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -579,6 +580,7 @@ exec_rt_fetch(Index rti, EState *estate)
 }
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
+extern RTEPermissionInfo *ExecgetRTEPermissionInfo(RangeTblEntry *rte, EState *estate);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 								   Index rti);
 
@@ -605,6 +607,7 @@ extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relIn
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
 extern TupleConversionMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate);
 
+extern Oid	ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate);
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9c6e8f5e13..75d13283b2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -617,6 +617,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rteperminfos; /* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	List		*es_part_prune_infos;	/* PlannedStmt.partPruneInfos */
 	List		*es_part_prune_results; /* QueryDesc.part_prune_results */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6112cd85c8..7fea34144f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -154,6 +154,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
+								 * rtable entries having perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -968,37 +970,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1054,11 +1025,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the containing struct's list of same; 0 if permissions need
+	 * not be checked for this RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1178,14 +1154,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns they need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 36abe4cf9e..cd169510f9 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrteperminfos;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 30f51414e9..a8be78dcbe 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -83,6 +83,8 @@ typedef struct PlannedStmt
 								 * indexes of range table entries of the leaf
 								 * partitions scanned by prunable subplans;
 								 * see AcquireExecutorLocks() */
+	List	   *permInfos;		/* list of RTEPermissionInfo nodes for rtable
+								 * entries needing one */
 
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..8ebd42b757 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -20,6 +20,8 @@
 extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 									 RangeTblEntry *rte, Index rti);
 
+extern Bitmapset *get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel);
+
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..ea84684acc 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rteperminfos: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rteperminfos;	/* list of RTEPermissionInfo nodes for each
+								 * RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rteperminfos.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rteperminfos entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..2f8d417709 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -99,6 +99,10 @@ extern ParseNamespaceItem *addRangeTableEntryForCTE(ParseState *pstate,
 extern ParseNamespaceItem *addRangeTableEntryForENR(ParseState *pstate,
 													RangeVar *rv,
 													bool inFromCl);
+extern RTEPermissionInfo *addRTEPermissionInfo(List **rteperminfos,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *getRTEPermissionInfo(List *rteperminfos,
+											   RangeTblEntry *rte);
 extern bool isLockedRefname(ParseState *pstate, const char *refname);
 extern void addNSItemToQuery(ParseState *pstate, ParseNamespaceItem *nsitem,
 							 bool addToJoinList,
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0ed319f11d 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -41,6 +41,9 @@ typedef enum ReplaceVarsNoMatchOption
 } ReplaceVarsNoMatchOption;
 
 
+extern List *CombineRangeTables(List *rtable1, List *rtable2,
+								List *rteperminfos2,
+								List **rteperminfos1);
 extern void OffsetVarNodes(Node *node, int offset, int sublevels_up);
 extern void ChangeVarNodes(Node *node, int rt_index, int new_index,
 						   int sublevels_up);
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..de1d3a4a94 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
 		allow = false;
 
 	if (allow)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 37c1c86473..e1cdaf9eb6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3584,6 +3584,18 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 RESET SESSION AUTHORIZATION;
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+ERROR:  permission denied for table ruletest_t3
+RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
  x 
 ---
@@ -3596,6 +3608,8 @@ SELECT * FROM ruletest_t2;
 (1 row)
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 DROP USER regress_rule_user1;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index bfb5f3b0bb..2f7cb8482a 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1309,11 +1309,26 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 
+RESET SESSION AUTHORIZATION;
+
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+
 RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
 SELECT * FROM ruletest_t2;
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 58daeca831..a2dfd5c9da 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2187,6 +2187,7 @@ RI_ConstraintInfo
 RI_QueryHashEntry
 RI_QueryKey
 RTEKind
+RTEPermissionInfo
 RWConflict
 RWConflictPoolHeader
 Range
@@ -3264,6 +3265,7 @@ fix_scan_expr_context
 fix_upper_expr_context
 fix_windowagg_cond_context
 flatten_join_alias_vars_context
+flatten_rtes_walker_context
 float4
 float4KEY
 float8
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-05 03:09  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-12-05 03:09 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Dec 2, 2022 at 8:13 PM Amit Langote <[email protected]> wrote:
> On Fri, Dec 2, 2022 at 7:00 PM Alvaro Herrera <[email protected]> wrote:
> > Great.  At this point I have no other comments, except that in both
> > parse_relation.c and rewriteManip.c you've chosen to add the new
> > functions at the bottom of each file, which is seldom a good choice.
> > I think in the case of CombineRangeTables it should be the very first
> > function in the file, before all the walker-type stuff; and for
> > Add/GetRTEPermissionInfo I would suggest that right below
> > addRangeTableEntryForENR might be a decent choice (need to fix the .h
> > files to match, of course.)
>
> Okay, I've moved the functions and their .h declarations to the places
> you suggest.  While at it, I also uncapitalized Add/Get, because
> that's how the nearby functions in the header are named.
>
> Thanks again for the review.  The patch looks much better than it did
> 3 weeks ago.

Rebased over 2605643a3a9d.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v33-0001-Rework-query-relation-permission-checking.patch (133.3K, ../../CA+HiwqHN6q-opBs8qsQKzWJ9LytmF7B6_ZBr1JdTSY4fiH53HQ@mail.gmail.com/2-v33-0001-Rework-query-relation-permission-checking.patch)
  download | inline diff:
From eb60c86dfbbd8687905bbfdde6b9c6f6a7a26d86 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Wed, 21 Jul 2021 21:33:19 +0900
Subject: [PATCH v33] Rework query relation permission checking

Currently, information about the permissions to be checked on
relations mentioned in a query is stored in their range table entries.
So the executor must scan the entire range table looking for relations
that need to have permissions checked.  This can make the permission
checking part of the executor initialization needlessly expensive when
many inheritance children are present in the range range.  While the
permissions need not be checked on the individual child relations, the
executor still must visit every range table entry to filter them out.

This commit moves the permission checking information out of the
range table entries into a new plan node called RTEPermissionInfo.
Every top-level (inheritance "root") RTE_RELATION entry in the range
table gets one and a list of those is maintained alongside the range
table.  The list is initialized by the parser when initializing the
range table.  The rewriter can add more entries to it as rules/views
are expanded.  Finally, the planner combines the lists of the
individual subqueries into one flat list that is passed down to the
executor.

To make it quick to find the RTEPermissionInfo entry belonging to a
given relation, RangeTblEntry gets a new Index field 'perminfoindex'
that stores the correponding RTEPermissionInfo's index in the query's
list of the latter.
---
 contrib/postgres_fdw/postgres_fdw.c           |  17 +-
 contrib/sepgsql/dml.c                         |  42 ++--
 contrib/sepgsql/hooks.c                       |  12 +-
 contrib/sepgsql/sepgsql.h                     |   3 +-
 src/backend/commands/copy.c                   |  23 ++-
 src/backend/commands/copyfrom.c               |  11 +-
 src/backend/commands/view.c                   |  33 ++-
 src/backend/executor/execMain.c               | 123 ++++++-----
 src/backend/executor/execParallel.c           |   1 +
 src/backend/executor/execUtils.c              | 146 +++++++++----
 src/backend/nodes/outfuncs.c                  |   6 +-
 src/backend/nodes/readfuncs.c                 |   6 +-
 src/backend/optimizer/plan/planner.c          |   6 +
 src/backend/optimizer/plan/setrefs.c          |  73 +++++--
 src/backend/optimizer/plan/subselect.c        |   9 +-
 src/backend/optimizer/prep/prepjointree.c     |  19 +-
 src/backend/optimizer/util/inherit.c          | 173 ++++++++++++----
 src/backend/optimizer/util/relnode.c          |  21 +-
 src/backend/parser/analyze.c                  |  68 ++++--
 src/backend/parser/parse_clause.c             |   9 +-
 src/backend/parser/parse_merge.c              |   9 +-
 src/backend/parser/parse_relation.c           | 178 +++++++++-------
 src/backend/parser/parse_target.c             |  18 +-
 src/backend/parser/parse_utilcmd.c            |   6 +-
 src/backend/replication/logical/worker.c      |  11 +-
 src/backend/rewrite/rewriteDefine.c           |  27 +--
 src/backend/rewrite/rewriteHandler.c          | 195 +++++++++---------
 src/backend/rewrite/rewriteManip.c            |  33 +++
 src/backend/rewrite/rowsecurity.c             |  25 ++-
 src/backend/utils/adt/ri_triggers.c           |  19 +-
 src/backend/utils/cache/relcache.c            |   4 +-
 src/include/commands/copyfrom_internal.h      |   3 +-
 src/include/executor/executor.h               |  11 +-
 src/include/nodes/execnodes.h                 |   1 +
 src/include/nodes/parsenodes.h                |  94 ++++++---
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/nodes/plannodes.h                 |   2 +
 src/include/optimizer/inherit.h               |   2 +
 src/include/parser/parse_node.h               |   9 +-
 src/include/parser/parse_relation.h           |   4 +
 src/include/rewrite/rewriteHandler.h          |   1 +
 src/include/rewrite/rewriteManip.h            |   3 +
 .../modules/test_oat_hooks/test_oat_hooks.c   |  12 +-
 src/test/regress/expected/rules.out           |  14 ++
 src/test/regress/sql/rules.sql                |  15 ++
 src/tools/pgindent/typedefs.list              |   2 +
 46 files changed, 968 insertions(+), 534 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 20c7b1ad05..1ceac2e0cf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -31,6 +31,7 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -657,8 +658,8 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	/*
 	 * If the table or the server is configured to use remote estimates,
 	 * identify which user to do remote access as during planning.  This
-	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
-	 * permissions, the query would have failed at runtime anyway.
+	 * should match what ExecCheckPermissions() does.  If we fail due to lack
+	 * of permissions, the query would have failed at runtime anyway.
 	 */
 	if (fpinfo->use_remote_estimate)
 	{
@@ -1809,7 +1810,8 @@ postgresPlanForeignModify(PlannerInfo *root,
 	else if (operation == CMD_UPDATE)
 	{
 		int			col;
-		Bitmapset  *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
+		RelOptInfo *rel = find_base_rel(root, resultRelation);
+		Bitmapset  *allUpdatedCols = get_rel_all_updated_cols(root, rel);
 
 		col = -1;
 		while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -2650,7 +2652,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 
 	/*
 	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
+	 * ExecCheckPermissions() does.
 	 */
 	userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
 
@@ -3975,11 +3977,8 @@ create_foreign_modify(EState *estate,
 	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
 	fmstate->rel = rel;
 
-	/*
-	 * Identify which user to do the remote access as.  This should match what
-	 * ExecCheckRTEPerms() does.
-	 */
-	userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	/* Identify which user to do the remote access as. */
+	userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
 
 	/* Get info about foreign table. */
 	table = GetForeignTable(RelationGetRelid(rel));
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 3bb98dfb06..02bc458238 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -23,6 +23,7 @@
 #include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "nodes/bitmapset.h"
+#include "parser/parsetree.h"
 #include "sepgsql.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
  * Entrypoint of the DML permission checks
  */
 bool
-sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
+sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos,
+					   bool abort_on_violation)
 {
 	ListCell   *lr;
 
-	foreach(lr, rangeTabls)
+	foreach(lr, rteperminfos)
 	{
-		RangeTblEntry *rte = lfirst(lr);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
 		uint32		required = 0;
 		List	   *tableIds;
 		ListCell   *li;
 
-		/*
-		 * Only regular relations shall be checked
-		 */
-		if (rte->rtekind != RTE_RELATION)
-			continue;
-
 		/*
 		 * Find out required permissions
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 			required |= SEPG_DB_TABLE__SELECT;
-		if (rte->requiredPerms & ACL_INSERT)
+		if (perminfo->requiredPerms & ACL_INSERT)
 			required |= SEPG_DB_TABLE__INSERT;
-		if (rte->requiredPerms & ACL_UPDATE)
+		if (perminfo->requiredPerms & ACL_UPDATE)
 		{
-			if (!bms_is_empty(rte->updatedCols))
+			if (!bms_is_empty(perminfo->updatedCols))
 				required |= SEPG_DB_TABLE__UPDATE;
 			else
 				required |= SEPG_DB_TABLE__LOCK;
 		}
-		if (rte->requiredPerms & ACL_DELETE)
+		if (perminfo->requiredPerms & ACL_DELETE)
 			required |= SEPG_DB_TABLE__DELETE;
 
 		/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 		 * checker routine will be invoked for each relations.
 		 */
-		if (!rte->inh)
-			tableIds = list_make1_oid(rte->relid);
+		if (!perminfo->inh)
+			tableIds = list_make1_oid(perminfo->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
 
 		foreach(li, tableIds)
 		{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 			 * child table has different attribute numbers, so we need to fix
 			 * up them.
 			 */
-			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->selectedCols);
-			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
-												   rte->insertedCols);
-			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
-												  rte->updatedCols);
+			selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->selectedCols);
+			insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												   perminfo->insertedCols);
+			updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
+												  perminfo->updatedCols);
 
 			/*
 			 * check permissions on individual tables
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 363ac06700..d8efb915de 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -35,7 +35,7 @@ PG_MODULE_MAGIC;
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /*
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
  * Entrypoint of DML permissions
  */
 static bool
-sepgsql_exec_check_perms(List *rangeTabls, bool abort)
+sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort)
 {
 	/*
 	 * If security provider is stacking and one of them replied 'false' at
 	 * least, we don't need to check any more.
 	 */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, abort))
+		!(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort))
 		return false;
 
-	if (!sepgsql_dml_privileges(rangeTabls, abort))
+	if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort))
 		return false;
 
 	return true;
@@ -471,8 +471,8 @@ _PG_init(void)
 	object_access_hook = sepgsql_object_access;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = sepgsql_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index f2a2c795bf..9e292271b7 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
 /*
  * dml.c
  */
-extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
+extern bool sepgsql_dml_privileges(List *rangeTabls, List *rteperminfos,
+								   bool abort_on_violation);
 
 /*
  * database.c
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db4c9dbc23..b8bd78d358 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
 		ParseNamespaceItem *nsitem;
-		RangeTblEntry *rte;
+		RTEPermissionInfo *perminfo;
 		TupleDesc	tupDesc;
 		List	   *attnums;
 		ListCell   *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
 											   NULL, false, false);
-		rte = nsitem->p_rte;
-		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
+
+		perminfo = nsitem->p_perminfo;
+		perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
@@ -150,15 +151,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
 		foreach(cur, attnums)
 		{
-			int			attno = lfirst_int(cur) -
-			FirstLowInvalidHeapAttributeNumber;
+			int			attno;
+			Bitmapset **bms;
 
-			if (is_from)
-				rte->insertedCols = bms_add_member(rte->insertedCols, attno);
-			else
-				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
+			attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
+			bms = is_from ? &perminfo->insertedCols : &perminfo->selectedCols;
+
+			*bms = bms_add_member(*bms, attno);
 		}
-		ExecCheckRTPerms(pstate->p_rtable, true);
+		ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
 
 		/*
 		 * Permission check for row security policies.
@@ -174,7 +175,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		 * If RLS is not enabled for this, then just fall through to the
 		 * normal non-filtering relation handling.
 		 */
-		if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
+		if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
 		{
 			SelectStmt *select;
 			ColumnRef  *cr;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 504afcb811..0371f51220 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
 	ExecInitResultRelation(estate, resultRelInfo, 1);
 
+	/*
+	 * Copy the RTEPermissionInfos into estate as well, so that
+	 * ExecGetInsertedCols() et al will work correctly.
+	 */
+	estate->es_rteperminfos = cstate->rteperminfos;
+
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
 
@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
 
 	initStringInfo(&cstate->attribute_buf);
 
-	/* Assign range table, we'll need it in CopyFrom. */
+	/* Assign range table and rteperminfos, we'll need them in CopyFrom. */
 	if (pstate)
+	{
 		cstate->range_table = pstate->p_rtable;
+		cstate->rteperminfos = pstate->p_rteperminfos;
+	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b5a0fc02e5..8e3c1efae4 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
  * by 2...
  *
  * These extra RT entries are not actually used in the query,
- * except for run-time locking and permission checking.
+ * except for run-time locking.
  *---------------------------------------------------------------
  */
 static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
+	RTEPermissionInfo *rte_perminfo1;
 	ParseState *pstate;
+	ListCell   *lc;
 
 	/*
 	 * Make a copy of the given parsetree.  It's not so much that we don't
@@ -405,15 +407,38 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 										   makeAlias("old", NIL),
 										   false, false);
 	rt_entry1 = nsitem->p_rte;
+	rte_perminfo1 = nsitem->p_perminfo;
 	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
 										   AccessShareLock,
 										   makeAlias("new", NIL),
 										   false, false);
 	rt_entry2 = nsitem->p_rte;
 
-	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->requiredPerms = 0;
-	rt_entry2->requiredPerms = 0;
+	/*
+	 * Add only the "old" RTEPermissionInfo at the head of view query's list
+	 * and update the other RTEs' perminfoindex accordingly.  When rewriting a
+	 * query on the view, ApplyRetrieveRule() will transfer the view
+	 * relation's permission details into this RTEPermissionInfo.  That's
+	 * needed because the view's RTE itself will be transposed into a subquery
+	 * RTE that can't carry the permission details; see the code stanza toward
+	 * the end of ApplyRetrieveRule() for how that's done.
+	 */
+	viewParse->rteperminfos = lcons(rte_perminfo1, viewParse->rteperminfos);
+	foreach(lc, viewParse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->perminfoindex > 0)
+			rte->perminfoindex += 1;
+	}
+
+	/*
+	 * Also make the "new" RTE's RTEPermissionInfo undiscoverable.  This is a
+	 * bit of a hack given that all the non-child RTE_RELATION entries really
+	 * should have a RTEPermissionInfo, but this dummy "new" RTE is going to
+	 * go away anyway in the very near future.
+	 */
+	rt_entry2->perminfoindex = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3293a65d15..f15c9abb68 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -55,6 +55,7 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -75,8 +76,8 @@ ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
 
-/* Hook for plugin to get control in ExecCheckRTPerms() */
-ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL;
+/* Hook for plugin to get control in ExecCheckPermissions() */
+ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook = NULL;
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
@@ -91,10 +92,10 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 						ScanDirection direction,
 						DestReceiver *dest,
 						bool execute_once);
-static bool ExecCheckRTEPerms(RangeTblEntry *rte);
-static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
-									  Bitmapset *modifiedCols,
-									  AclMode requiredPerms);
+static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
+static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
+										 Bitmapset *modifiedCols,
+										 AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
 static char *ExecBuildSlotValueDescription(Oid reloid,
 										   TupleTableSlot *slot,
@@ -607,8 +608,8 @@ ExecutorRewind(QueryDesc *queryDesc)
 
 
 /*
- * ExecCheckRTPerms
- *		Check access permissions for all relations listed in a range table.
+ * ExecCheckPermissions
+ *		Check access permissions of relations mentioned in a query
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
@@ -618,73 +619,65 @@ ExecutorRewind(QueryDesc *queryDesc)
  * passing, then RLS also needs to be consulted (and check_enable_rls()).
  *
  * See rewrite/rowsecurity.c.
+ *
+ * NB: rangeTable is no longer used by us, but kept around for the hooks that
+ * might still want to look at the RTEs.
  */
 bool
-ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
+ExecCheckPermissions(List *rangeTable, List *rteperminfos,
+					 bool ereport_on_violation)
 {
 	ListCell   *l;
 	bool		result = true;
 
-	foreach(l, rangeTable)
+	foreach(l, rteperminfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		result = ExecCheckRTEPerms(rte);
+		Assert(OidIsValid(perminfo->relid));
+		result = ExecCheckOneRelPerms(perminfo);
 		if (!result)
 		{
-			Assert(rte->rtekind == RTE_RELATION);
 			if (ereport_on_violation)
-				aclcheck_error(ACLCHECK_NO_PRIV, get_relkind_objtype(get_rel_relkind(rte->relid)),
-							   get_rel_name(rte->relid));
+				aclcheck_error(ACLCHECK_NO_PRIV,
+							   get_relkind_objtype(get_rel_relkind(perminfo->relid)),
+							   get_rel_name(perminfo->relid));
 			return false;
 		}
 	}
 
-	if (ExecutorCheckPerms_hook)
-		result = (*ExecutorCheckPerms_hook) (rangeTable,
-											 ereport_on_violation);
+	if (ExecutorCheckPermissions_hook)
+		result = (*ExecutorCheckPermissions_hook) (rangeTable, rteperminfos,
+												   ereport_on_violation);
 	return result;
 }
 
 /*
- * ExecCheckRTEPerms
- *		Check access permissions for a single RTE.
+ * ExecCheckOneRelPerms
+ *		Check access permissions for a single relation.
  */
 static bool
-ExecCheckRTEPerms(RangeTblEntry *rte)
+ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
 	AclMode		requiredPerms;
 	AclMode		relPerms;
 	AclMode		remainingPerms;
-	Oid			relOid;
 	Oid			userid;
+	Oid			relOid = perminfo->relid;
 
-	/*
-	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked when the function is prepared for execution.  Join, subquery,
-	 * and special RTEs need no checks.
-	 */
-	if (rte->rtekind != RTE_RELATION)
-		return true;
-
-	/*
-	 * No work if requiredPerms is empty.
-	 */
-	requiredPerms = rte->requiredPerms;
-	if (requiredPerms == 0)
-		return true;
-
-	relOid = rte->relid;
+	requiredPerms = perminfo->requiredPerms;
+	Assert(requiredPerms != 0);
 
 	/*
 	 * userid to check as: current user unless we have a setuid indication.
 	 *
 	 * Note: GetUserId() is presently fast enough that there's no harm in
-	 * calling it separately for each RTE.  If that stops being true, we could
-	 * call it once in ExecCheckRTPerms and pass the userid down from there.
-	 * But for now, no need for the extra clutter.
+	 * calling it separately for each relation.  If that stops being true, we
+	 * could call it once in ExecCheckPermissions and pass the userid down
+	 * from there.  But for now, no need for the extra clutter.
 	 */
-	userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	userid = OidIsValid(perminfo->checkAsUser) ?
+		perminfo->checkAsUser : GetUserId();
 
 	/*
 	 * We must have *all* the requiredPerms bits, but some of the bits can be
@@ -718,14 +711,14 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 			 * example, SELECT COUNT(*) FROM table), allow the query if we
 			 * have SELECT on any column of the rel, as per SQL spec.
 			 */
-			if (bms_is_empty(rte->selectedCols))
+			if (bms_is_empty(perminfo->selectedCols))
 			{
 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
 											  ACLMASK_ANY) != ACLCHECK_OK)
 					return false;
 			}
 
-			while ((col = bms_next_member(rte->selectedCols, col)) >= 0)
+			while ((col = bms_next_member(perminfo->selectedCols, col)) >= 0)
 			{
 				/* bit #s are offset by FirstLowInvalidHeapAttributeNumber */
 				AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
@@ -750,29 +743,31 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 		 * Basically the same for the mod columns, for both INSERT and UPDATE
 		 * privilege as specified by remainingPerms.
 		 */
-		if (remainingPerms & ACL_INSERT && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->insertedCols,
-																	  ACL_INSERT))
+		if (remainingPerms & ACL_INSERT &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->insertedCols,
+										  ACL_INSERT))
 			return false;
 
-		if (remainingPerms & ACL_UPDATE && !ExecCheckRTEPermsModified(relOid,
-																	  userid,
-																	  rte->updatedCols,
-																	  ACL_UPDATE))
+		if (remainingPerms & ACL_UPDATE &&
+			!ExecCheckPermissionsModified(relOid,
+										  userid,
+										  perminfo->updatedCols,
+										  ACL_UPDATE))
 			return false;
 	}
 	return true;
 }
 
 /*
- * ExecCheckRTEPermsModified
- *		Check INSERT or UPDATE access permissions for a single RTE (these
+ * ExecCheckPermissionsModified
+ *		Check INSERT or UPDATE access permissions for a single relation (these
  *		are processed uniformly).
  */
 static bool
-ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
-						  AclMode requiredPerms)
+ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols,
+							 AclMode requiredPerms)
 {
 	int			col = -1;
 
@@ -826,17 +821,14 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 * Fail if write permissions are requested in parallel mode for table
 	 * (temp or non-temp), otherwise fail for any non-temp table.
 	 */
-	foreach(l, plannedstmt->rtable)
+	foreach(l, plannedstmt->permInfos)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-		if (rte->rtekind != RTE_RELATION)
-			continue;
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
 
-		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+		if ((perminfo->requiredPerms & (~ACL_SELECT)) == 0)
 			continue;
 
-		if (isTempNamespace(get_rel_namespace(rte->relid)))
+		if (isTempNamespace(get_rel_namespace(perminfo->relid)))
 			continue;
 
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
@@ -869,9 +861,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	int			i;
 
 	/*
-	 * Do permissions checks
+	 * Do permissions checks and save the list for later use.
 	 */
-	ExecCheckRTPerms(rangeTable, true);
+	ExecCheckPermissions(rangeTable, plannedstmt->permInfos, true);
+	estate->es_rteperminfos = plannedstmt->permInfos;
 
 	/*
 	 * initialize the node's execution state
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 917079a034..6f3f014cca 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -187,6 +187,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->planTree = plan;
 	pstmt->partPruneInfos = estate->es_part_prune_infos;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->permInfos = estate->es_rteperminfos;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 044bf3f491..096058b296 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -57,6 +57,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -67,6 +68,7 @@
 
 static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, int varno, TupleDesc tupdesc);
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
+static inline RTEPermissionInfo *GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate);
 
 
 /* ----------------------------------------------------------------
@@ -1297,72 +1299,48 @@ ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate)
 Bitmapset *
 ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/*
-	 * The columns are stored in the range table entry.  If this ResultRelInfo
-	 * represents a partition routing target, and doesn't have an entry of its
-	 * own in the range table, fetch the parent's RTE and map the columns to
-	 * the order they are in the partition.
-	 */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->insertedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (map != NULL)
-			return execute_attr_map_cols(map->attrMap, rte->insertedCols);
-		else
-			return rte->insertedCols;
-	}
-	else
-	{
-		/*
-		 * The relation isn't in the range table and it isn't a partition
-		 * routing target.  This ResultRelInfo must've been created only for
-		 * firing triggers and the relation is not being inserted into.  (See
-		 * ExecGetTriggerResultRel.)
-		 */
-		return NULL;
+		if (map)
+			return execute_attr_map_cols(map->attrMap, perminfo->insertedCols);
 	}
+
+	return perminfo->insertedCols;
 }
 
 /* Return a bitmap representing columns being updated */
 Bitmapset *
 ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
-	if (relinfo->ri_RangeTableIndex != 0)
-	{
-		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relinfo, estate);
 
-		return rte->updatedCols;
-	}
-	else if (relinfo->ri_RootResultRelInfo)
+	if (perminfo == NULL)
+		return NULL;
+
+	/* Map the columns to child's attribute numbers if needed. */
+	if (relinfo->ri_RootResultRelInfo)
 	{
-		ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo;
-		RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate);
 		TupleConversionMap *map = ExecGetRootToChildMap(relinfo, estate);
 
-		if (map != NULL)
-			return execute_attr_map_cols(map->attrMap, rte->updatedCols);
-		else
-			return rte->updatedCols;
+		if (map)
+			return execute_attr_map_cols(map->attrMap, perminfo->updatedCols);
 	}
-	else
-		return NULL;
+
+	return perminfo->updatedCols;
 }
 
 /* Return a bitmap representing generated columns being updated */
 Bitmapset *
 ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 {
-	/* see ExecGetInsertedCols() */
 	if (relinfo->ri_RangeTableIndex != 0)
 	{
 		RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate);
@@ -1391,3 +1369,83 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate)
 	return bms_union(ExecGetUpdatedCols(relinfo, estate),
 					 ExecGetExtraUpdatedCols(relinfo, estate));
 }
+
+/*
+ * GetResultRTEPermissionInfo
+ *		Looks up RTEPermissionInfo for ExecGet*Cols() routines
+ */
+static inline RTEPermissionInfo *
+GetResultRTEPermissionInfo(ResultRelInfo *relinfo, EState *estate)
+{
+	Index		rti;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo = NULL;
+
+	if (relinfo->ri_RootResultRelInfo)
+	{
+		/*
+		 * For inheritance child result relations (a partition routing target
+		 * of an INSERT or a child UPDATE target), this returns the root
+		 * parent's RTE to fetch the RTEPermissionInfo because that's the only
+		 * one that has one assigned.
+		 */
+		rti = relinfo->ri_RootResultRelInfo->ri_RangeTableIndex;
+	}
+	else if (relinfo->ri_RangeTableIndex != 0)
+	{
+		/*
+		 * Non-child result relation should have their own RTEPermissionInfo.
+		 */
+		rti = relinfo->ri_RangeTableIndex;
+	}
+	else
+	{
+		/*
+		 * The relation isn't in the range table and it isn't a partition
+		 * routing target.  This ResultRelInfo must've been created only for
+		 * firing triggers and the relation is not being inserted into.  (See
+		 * ExecGetTriggerResultRel.)
+		 */
+		rti = 0;
+	}
+
+	if (rti > 0)
+	{
+		rte = exec_rt_fetch(rti, estate);
+		perminfo = ExecgetRTEPermissionInfo(rte, estate);
+	}
+
+	return perminfo;
+}
+
+/*
+ * ExecgetRTEPermissionInfo
+ *		Returns the RTEPermissionInfo contained in estate->es_rteperminfos
+ *		pointed to by the RTE
+ */
+RTEPermissionInfo *
+ExecgetRTEPermissionInfo(RangeTblEntry *rte, EState *estate)
+{
+	Assert(estate->es_rteperminfos != NIL);
+	return getRTEPermissionInfo(estate->es_rteperminfos, rte);
+}
+
+/*
+ * GetResultRelCheckAsUser
+ *		Returns the user to modify passed-in result relation as
+ *
+ * The user is chosen by looking up the relation's or, if a child table, its
+ * root parent's RTEPermissionInfo.
+ */
+Oid
+ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate)
+{
+	RTEPermissionInfo *perminfo = GetResultRTEPermissionInfo(relInfo, estate);
+
+	/* XXX - maybe ok to return GetUserId() in this case? */
+	if (perminfo == NULL)
+		elog(ERROR, "no RTEPermissionInfo found for result relation with OID %u",
+			 RelationGetRelid(relInfo->ri_RelationDesc));
+
+	return perminfo->checkAsUser ? perminfo->checkAsUser : GetUserId();
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8f150e9a2e..59b0fdeb62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -507,6 +507,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
 			WRITE_NODE_FIELD(tablesample);
+			WRITE_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -560,11 +561,6 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_UINT64_FIELD(requiredPerms);
-	WRITE_OID_FIELD(checkAsUser);
-	WRITE_BITMAPSET_FIELD(selectedCols);
-	WRITE_BITMAPSET_FIELD(insertedCols);
-	WRITE_BITMAPSET_FIELD(updatedCols);
 	WRITE_BITMAPSET_FIELD(extraUpdatedCols);
 	WRITE_NODE_FIELD(securityQuals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b01f55fb4f..1161671fa4 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -478,6 +478,7 @@ _readRangeTblEntry(void)
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
 			READ_NODE_FIELD(tablesample);
+			READ_UINT_FIELD(perminfoindex);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
@@ -541,11 +542,6 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(lateral);
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_UINT_FIELD(requiredPerms);
-	READ_OID_FIELD(checkAsUser);
-	READ_BITMAPSET_FIELD(selectedCols);
-	READ_BITMAPSET_FIELD(insertedCols);
-	READ_BITMAPSET_FIELD(updatedCols);
 	READ_BITMAPSET_FIELD(extraUpdatedCols);
 	READ_NODE_FIELD(securityQuals);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a96d316dca..3cb1fb9334 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -306,6 +307,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->subroots = NIL;
 	glob->rewindPlanIDs = NULL;
 	glob->finalrtable = NIL;
+	glob->finalrteperminfos = NIL;
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->appendRelations = NIL;
@@ -493,6 +495,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/* final cleanup of the plan */
 	Assert(glob->finalrtable == NIL);
+	Assert(glob->finalrteperminfos == NIL);
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->appendRelations == NIL);
@@ -523,6 +526,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->containsInitialPruning = glob->containsInitialPruning;
 	result->rtable = glob->finalrtable;
 	result->minLockRelids = glob->minLockRelids;
+	result->permInfos = glob->finalrteperminfos;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
@@ -6268,6 +6272,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	rte->inh = false;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	addRTEPermissionInfo(&query->rteperminfos, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
@@ -6395,6 +6400,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	rte->inh = true;
 	rte->inFromCl = true;
 	query->rtable = list_make1(rte);
+	addRTEPermissionInfo(&query->rteperminfos, rte);
 
 	/* Set up RTE/RelOptInfo arrays */
 	setup_simple_rel_arrays(root);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 44ffe71c49..cbd8b9a0c0 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -24,6 +24,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -78,6 +79,13 @@ typedef struct
 	int			newvarno;
 } fix_windowagg_cond_context;
 
+/* Context info for flatten_rtes_walker() */
+typedef struct
+{
+	PlannerGlobal *glob;
+	Query	   *query;
+} flatten_rtes_walker_context;
+
 /*
  * Selecting the best alternative in an AlternativeSubPlan expression requires
  * estimating how many times that expression will be evaluated.  For an
@@ -113,8 +121,9 @@ typedef struct
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
-static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
-static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
+static bool flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt);
+static void add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
+								   RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 										  IndexOnlyScan *plan,
@@ -426,6 +435,9 @@ set_plan_references(PlannerInfo *root, Plan *plan)
  * Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
  *
  * This can recurse into subquery plans; "recursing" is true if so.
+ *
+ * This also seems like a good place to add the query's RTEPermissionInfos to
+ * the flat rteperminfos.
  */
 static void
 add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
@@ -446,7 +458,7 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 		if (!recursing || rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(glob, root->parse->rteperminfos, rte);
 	}
 
 	/*
@@ -513,18 +525,21 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 /*
  * Extract RangeTblEntries from a subquery that was never planned at all
  */
+
 static void
 flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
 {
+	flatten_rtes_walker_context cxt = {glob, rte->subquery};
+
 	/* Use query_tree_walker to find all RTEs in the parse tree */
 	(void) query_tree_walker(rte->subquery,
 							 flatten_rtes_walker,
-							 (void *) glob,
+							 (void *) &cxt,
 							 QTW_EXAMINE_RTES_BEFORE);
 }
 
 static bool
-flatten_rtes_walker(Node *node, PlannerGlobal *glob)
+flatten_rtes_walker(Node *node, flatten_rtes_walker_context *cxt)
 {
 	if (node == NULL)
 		return false;
@@ -534,33 +549,38 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
 
 		/* As above, we need only save relation RTEs */
 		if (rte->rtekind == RTE_RELATION)
-			add_rte_to_flat_rtable(glob, rte);
+			add_rte_to_flat_rtable(cxt->glob, cxt->query->rteperminfos, rte);
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into subselects */
+		/*
+		 * Recurse into subselects.  Must update cxt->query to this query so
+		 * that the rtable and rteperminfos correspond with each other.
+		 */
+		cxt->query = (Query *) node;
 		return query_tree_walker((Query *) node,
 								 flatten_rtes_walker,
-								 (void *) glob,
+								 (void *) cxt,
 								 QTW_EXAMINE_RTES_BEFORE);
 	}
 	return expression_tree_walker(node, flatten_rtes_walker,
-								  (void *) glob);
+								  (void *) cxt);
 }
 
 /*
- * Add (a copy of) the given RTE to the final rangetable
+ * Add (a copy of) the given RTE to the final rangetable and also the
+ * corresponding RTEPermissionInfo, if any, to final rteperminfos.
  *
  * In the flat rangetable, we zero out substructure pointers that are not
  * needed by the executor; this reduces the storage space and copying cost
- * for cached plans.  We keep only the ctename, alias and eref Alias fields,
- * which are needed by EXPLAIN, and the selectedCols, insertedCols,
- * updatedCols, and extraUpdatedCols bitmaps, which are needed for
- * executor-startup permissions checking and for trigger event checking.
+ * for cached plans.  We keep only the ctename, alias, eref Alias fields,
+ * which are needed by EXPLAIN, and perminfoindex which is needed by the
+ * executor to fetch the RTE's RTEPermissionInfo.
  */
 static void
-add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
+add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
+					   RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
 
@@ -598,6 +618,29 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 */
 	if (newrte->rtekind == RTE_RELATION)
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+	/*
+	 * Add a copy of the RTEPermissionInfo, if any, corresponding to this RTE
+	 * to the flattened global list.
+	 */
+	if (rte->perminfoindex > 0)
+	{
+		RTEPermissionInfo *perminfo;
+		RTEPermissionInfo *newperminfo;
+
+		/* Get the existing one from this query's rteperminfos. */
+		perminfo = getRTEPermissionInfo(rteperminfos, newrte);
+
+		/*
+		 * Add a new one to finalrteperminfos and copy the contents of the
+		 * existing one into it.  Note that addRTEPermissionInfo() also
+		 * updates newrte->perminfoindex to point to newperminfo in
+		 * finalrteperminfos.
+		 */
+		newrte->perminfoindex = 0;	/* expected by addRTEPermissionInfo() */
+		newperminfo = addRTEPermissionInfo(&glob->finalrteperminfos, newrte);
+		memcpy(newperminfo, perminfo, sizeof(RTEPermissionInfo));
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 92e3338584..844971dba7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1496,8 +1496,13 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	if (!bms_is_subset(upper_varnos, available_rels))
 		return NULL;
 
-	/* Now we can attach the modified subquery rtable to the parent */
-	parse->rtable = list_concat(parse->rtable, subselect->rtable);
+	/*
+	 * Now we can attach the modified subquery rtable to the parent. This also
+	 * adds subquery's RTEPermissionInfos into the upper query.
+	 */
+	parse->rtable = CombineRangeTables(parse->rtable, subselect->rtable,
+									   subselect->rteperminfos,
+									   &parse->rteperminfos);
 
 	/*
 	 * And finally, build the JoinExpr node.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 2ea3ca734e..3eb006e1ad 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -176,13 +176,6 @@ transform_MERGE_to_join(Query *parse)
 	joinrte->lateral = false;
 	joinrte->inh = false;
 	joinrte->inFromCl = true;
-	joinrte->requiredPerms = 0;
-	joinrte->checkAsUser = InvalidOid;
-	joinrte->selectedCols = NULL;
-	joinrte->insertedCols = NULL;
-	joinrte->updatedCols = NULL;
-	joinrte->extraUpdatedCols = NULL;
-	joinrte->securityQuals = NIL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1209,8 +1202,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 * Now append the adjusted rtable entries to upper query. (We hold off
 	 * until after fixing the upper rtable entries; no point in running that
 	 * code on the subquery ones too.)
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	parse->rtable = list_concat(parse->rtable, subquery->rtable);
+	parse->rtable = CombineRangeTables(parse->rtable, subquery->rtable,
+									   subquery->rteperminfos,
+									   &parse->rteperminfos);
 
 	/*
 	 * Pull up any FOR UPDATE/SHARE markers, too.  (OffsetVarNodes already
@@ -1347,8 +1344,12 @@ pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
 
 	/*
 	 * Append child RTEs to parent rtable.
+	 *
+	 * This also adds subquery's RTEPermissionInfos into the upper query.
 	 */
-	root->parse->rtable = list_concat(root->parse->rtable, rtable);
+	root->parse->rtable = CombineRangeTables(root->parse->rtable, rtable,
+											 subquery->rteperminfos,
+											 &root->parse->rteperminfos);
 
 	/*
 	 * Recursively scan the subquery's setOperations tree and add
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3d270e91d6..3d6f75baee 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -30,6 +30,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
 #include "utils/rel.h"
@@ -38,6 +39,7 @@
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
+									   Bitmapset *parent_updatedCols,
 									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
@@ -47,6 +49,10 @@ static void expand_single_inheritance_child(PlannerInfo *root,
 											Index *childRTindex_p);
 static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 									  List *translated_vars);
+static Bitmapset *translate_col_privs_multilevel(PlannerInfo *root,
+												 RelOptInfo *rel,
+												 RelOptInfo *top_parent_rel,
+												 Bitmapset *top_parent_cols);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
 									  RangeTblEntry *rte, Index rti);
 
@@ -131,6 +137,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
+		RTEPermissionInfo *perminfo;
+
+		perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+
 		/*
 		 * Partitioned table, so set up for partitioning.
 		 */
@@ -141,7 +151,9 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation,
+								   perminfo->updatedCols,
+								   oldrc, lockmode);
 	}
 	else
 	{
@@ -305,6 +317,7 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
+						   Bitmapset *parent_updatedCols,
 						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
 {
 	PartitionDesc partdesc;
@@ -324,14 +337,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 	/*
 	 * Note down whether any partition key cols are being updated. Though it's
-	 * the root partitioned table's updatedCols we are interested in, we
-	 * instead use parentrte to get the updatedCols. This is convenient
-	 * because parentrte already has the root partrel's updatedCols translated
-	 * to match the attribute ordering of parentrel.
+	 * the root partitioned table's updatedCols we are interested in,
+	 * parent_updatedCols provided by the caller contains the root partrel's
+	 * updatedCols translated to match the attribute ordering of parentrel.
 	 */
 	if (!root->partColsUpdated)
 		root->partColsUpdated =
-			has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+			has_partition_attrs(parentrel, parent_updatedCols, NULL);
 
 	/*
 	 * There shouldn't be any generated columns in the partition key.
@@ -402,9 +414,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			AppendRelInfo *appinfo = root->append_rel_array[childRTindex];
+			Bitmapset  *child_updatedCols;
+
+			child_updatedCols = translate_col_privs(parent_updatedCols,
+													appinfo->translated_vars);
+
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel,
+									   child_updatedCols,
+									   top_parentrc, lockmode);
+		}
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -451,17 +473,15 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	/*
 	 * Build an RTE for the child, and attach to query's rangetable list. We
 	 * copy most scalar fields of the parent's RTE, but replace relation OID,
-	 * relkind, and inh for the child.  Also, set requiredPerms to zero since
-	 * all required permissions checks are done on the original RTE. Likewise,
-	 * set the child's securityQuals to empty, because we only want to apply
-	 * the parent's RLS conditions regardless of what RLS properties
-	 * individual children may have.  (This is an intentional choice to make
-	 * inherited RLS work like regular permissions checks.) The parent
-	 * securityQuals will be propagated to children along with other base
-	 * restriction clauses, so we don't need to do it here.  Other
-	 * infrastructure of the parent RTE has to be translated to match the
-	 * child table's column ordering, which we do below, so a "flat" copy is
-	 * sufficient to start with.
+	 * relkind, and inh for the child.  Set the child's securityQuals to
+	 * empty, because we only want to apply the parent's RLS conditions
+	 * regardless of what RLS properties individual children may have.
+	 * (This is an intentional choice to make inherited RLS work like regular
+	 * permissions checks.) The parent securityQuals will be propagated to
+	 * children along with other base restriction clauses, so we don't need
+	 * to do it here.  Other infrastructure of the parent RTE has to be
+	 * translated to match the child table's column ordering, which we do
+	 * below, so a "flat" copy is sufficient to start with.
 	 */
 	childrte = makeNode(RangeTblEntry);
 	memcpy(childrte, parentrte, sizeof(RangeTblEntry));
@@ -476,9 +496,16 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 	else
 		childrte->inh = false;
-	childrte->requiredPerms = 0;
 	childrte->securityQuals = NIL;
 
+	/*
+	 * No permission checking for the child RTE unless it's the parent
+	 * relation in its child role, which only applies to traditional
+	 * inheritance.
+	 */
+	if (childOID != parentOID)
+		childrte->perminfoindex = 0;
+
 	/* Link not-yet-fully-filled child RTE into data structures */
 	parse->rtable = lappend(parse->rtable, childrte);
 	childRTindex = list_length(parse->rtable);
@@ -539,33 +566,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	childrte->alias = childrte->eref = makeAlias(parentrte->eref->aliasname,
 												 child_colnames);
 
-	/*
-	 * Translate the column permissions bitmaps to the child's attnums (we
-	 * have to build the translated_vars list before we can do this).  But if
-	 * this is the parent table, we can just duplicate the parent's bitmaps.
-	 *
-	 * Note: we need to do this even though the executor won't run any
-	 * permissions checks on the child RTE.  The insertedCols/updatedCols
-	 * bitmaps may be examined for trigger-firing purposes.
-	 */
+	/* Translate the bitmapset of generated columns being updated. */
 	if (childOID != parentOID)
-	{
-		childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
-													 appinfo->translated_vars);
-		childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
-													 appinfo->translated_vars);
-		childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
-													appinfo->translated_vars);
 		childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
 														 appinfo->translated_vars);
-	}
 	else
-	{
-		childrte->selectedCols = bms_copy(parentrte->selectedCols);
-		childrte->insertedCols = bms_copy(parentrte->insertedCols);
-		childrte->updatedCols = bms_copy(parentrte->updatedCols);
 		childrte->extraUpdatedCols = bms_copy(parentrte->extraUpdatedCols);
-	}
 
 	/*
 	 * Store the RTE and appinfo in the respective PlannerInfo arrays, which
@@ -648,6 +654,54 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	}
 }
 
+/*
+ * get_rel_all_updated_cols
+ * 		Returns the set of columns of a given "simple" relation that are
+ * 		updated by this query.
+ */
+Bitmapset *
+get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel)
+{
+	Index		relid;
+	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
+	Bitmapset  *updatedCols,
+			   *extraUpdatedCols;
+
+	Assert(root->parse->commandType == CMD_UPDATE);
+	Assert(IS_SIMPLE_REL(rel));
+
+	/*
+	 * We obtain updatedCols and extraUpdatedCols for the query's result
+	 * relation.  Then, if necessary, we map it to the column numbers of the
+	 * relation for which they were requested.
+	 */
+	relid = root->parse->resultRelation;
+	rte = planner_rt_fetch(relid, root);
+	perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+
+	updatedCols = perminfo->updatedCols;
+	extraUpdatedCols = rte->extraUpdatedCols;
+
+	/*
+	 * For "other" rels, we must look up the root parent relation mentioned in
+	 * the query, and translate the column numbers.
+	 */
+	if (rel->relid != relid)
+	{
+		RelOptInfo *top_parent_rel = find_base_rel(root, relid);
+
+		Assert(IS_OTHER_REL(rel));
+
+		updatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+													 updatedCols);
+		extraUpdatedCols = translate_col_privs_multilevel(root, rel, top_parent_rel,
+														  extraUpdatedCols);
+	}
+
+	return bms_union(updatedCols, extraUpdatedCols);
+}
+
 /*
  * translate_col_privs
  *	  Translate a bitmapset representing per-column privileges from the
@@ -866,3 +920,40 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * translate_col_privs_multilevel
+ * 		Recursively translates the column numbers contained in
+ * 		'top_parent_cols' to the columns numbers of a descendent relation
+ * 		given by 'relid'
+ */
+static Bitmapset *
+translate_col_privs_multilevel(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *top_parent_rel,
+							   Bitmapset *top_parent_cols)
+{
+	Bitmapset  *result;
+	AppendRelInfo *appinfo;
+
+	if (top_parent_cols == NULL)
+		return NULL;
+
+	/* Recurse if immediate parent is not the top parent. */
+	if (rel->parent != top_parent_rel)
+	{
+		if (rel->parent)
+			result = translate_col_privs_multilevel(root, rel->parent,
+													top_parent_rel,
+													top_parent_cols);
+		else
+			elog(ERROR, "rel with relid %u is not a child rel", rel->relid);
+	}
+
+	Assert(root->append_rel_array != NULL);
+	appinfo = root->append_rel_array[rel->relid];
+	Assert(appinfo != NULL);
+
+	result = translate_col_privs(top_parent_cols, appinfo->translated_vars);
+
+	return result;
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d7b4434e7f..7085cf3c41 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parse_relation.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -223,7 +224,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
 	rel->serverid = InvalidOid;
-	rel->userid = rte->checkAsUser;
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/*
+		 * Get the userid from the relation's RTEPermissionInfo, though only
+		 * the tables mentioned in query are assigned RTEPermissionInfos.
+		 * Child relations (otherrels) simply use the parent's value.
+		 */
+		if (parent == NULL)
+		{
+			RTEPermissionInfo *perminfo;
+
+			perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+			rel->userid = perminfo->checkAsUser;
+		}
+		else
+			rel->userid = parent->userid;
+	}
+	else
+		rel->userid = InvalidOid;
 	rel->useridiscurrent = false;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6688c2a865..2e593aed2b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -518,6 +518,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -546,11 +547,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *exprList = NIL;
 	bool		isGeneralSelect;
 	List	   *sub_rtable;
+	List	   *sub_rteperminfos;
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	ParseNamespaceItem *nsitem;
-	RangeTblEntry *rte;
+	RTEPermissionInfo *perminfo;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -594,17 +596,19 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * If a non-nil rangetable/namespace was passed in, and we are doing
-	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
-	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
-	 * that case we want the rule's OLD and NEW rtable entries to appear as
-	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
-	 * The SELECT's joinlist is not affected however.  We must do this before
-	 * adding the target table to the INSERT's rtable.
+	 * INSERT/SELECT, arrange to pass the rangetable/rteperminfos/namespace
+	 * down to the SELECT.  This can only happen if we are inside a CREATE
+	 * RULE, and in that case we want the rule's OLD and NEW rtable entries to
+	 * appear as part of the SELECT's rtable, not as outer references for it.
+	 * (Kluge!) The SELECT's joinlist is not affected however.  We must do
+	 * this before adding the target table to the INSERT's rtable.
 	 */
 	if (isGeneralSelect)
 	{
 		sub_rtable = pstate->p_rtable;
 		pstate->p_rtable = NIL;
+		sub_rteperminfos = pstate->p_rteperminfos;
+		pstate->p_rteperminfos = NIL;
 		sub_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
 	}
@@ -669,6 +673,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * the target column's type, which we handle below.
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_rteperminfos = sub_rteperminfos;
 		sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
 		sub_pstate->p_namespace = sub_namespace;
 		sub_pstate->p_resolve_unknowns = false;
@@ -894,7 +899,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_nsitem->p_rte;
+	perminfo = pstate->p_target_nsitem->p_perminfo;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -910,8 +915,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
-		rte->insertedCols = bms_add_member(rte->insertedCols,
-										   attr_num - FirstLowInvalidHeapAttributeNumber);
+		perminfo->insertedCols = bms_add_member(perminfo->insertedCols,
+												attr_num - FirstLowInvalidHeapAttributeNumber);
 	}
 
 	/*
@@ -938,6 +943,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1096,8 +1102,6 @@ transformOnConflictClause(ParseState *pstate,
 		 * (We'll check the actual target relation, instead.)
 		 */
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		exclRte->requiredPerms = 0;
-		/* other permissions fields in exclRte are already empty */
 
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
@@ -1391,6 +1395,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1619,6 +1624,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 									  linitial(stmt->lockingClause))->strength))));
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -1865,6 +1871,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->limitOption = stmt->limitOption;
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -2339,6 +2346,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	if (pstate->p_resolve_unknowns)
 		resolveTargetListUnknowns(pstate, qry->targetList);
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -2405,6 +2413,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -2423,7 +2432,7 @@ List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
-	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	ListCell   *orig_tl;
 	ListCell   *tl;
 
@@ -2435,7 +2444,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_nsitem->p_rte;
+	target_perminfo = pstate->p_target_nsitem->p_perminfo;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
@@ -2476,8 +2485,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							  origTarget->location);
 
 		/* Mark the target column as requiring update permissions */
-		target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
-												 attrno - FirstLowInvalidHeapAttributeNumber);
+		target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols,
+													  attrno - FirstLowInvalidHeapAttributeNumber);
 
 		orig_tl = lnext(origTlist, orig_tl);
 	}
@@ -2764,6 +2773,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 												   &qry->targetList);
 
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -3242,9 +3252,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
-									   pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					{
+						RTEPermissionInfo *perminfo;
+
+						applyLockingClause(qry, i,
+										   lc->strength,
+										   lc->waitPolicy,
+										   pushedDown);
+						perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
+						perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
@@ -3324,9 +3341,16 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							applyLockingClause(qry, i, lc->strength,
-											   lc->waitPolicy, pushedDown);
-							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							{
+								RTEPermissionInfo *perminfo;
+
+								applyLockingClause(qry, i,
+												   lc->strength,
+												   lc->waitPolicy,
+												   pushedDown);
+								perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
+								perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+							}
 							break;
 						case RTE_SUBQUERY:
 							applyLockingClause(qry, i, lc->strength,
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e01c0734d1..856839f379 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -225,7 +225,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	nsitem->p_rte->requiredPerms = requiredPerms;
+	nsitem->p_perminfo->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
@@ -3226,16 +3226,17 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
+			RTEPermissionInfo *perminfo = pstate->p_target_nsitem->p_perminfo;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
 													   false, constraint);
 
 			/* Make sure the rel as a whole is marked for SELECT access */
-			rte->requiredPerms |= ACL_SELECT;
+			perminfo->requiredPerms |= ACL_SELECT;
 			/* Mark the constrained columns as requiring SELECT access */
-			rte->selectedCols = bms_add_members(rte->selectedCols, conattnos);
+			perminfo->selectedCols = bms_add_members(perminfo->selectedCols,
+													 conattnos);
 		}
 	}
 
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 62c2ff69f0..3844f2b45f 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -215,6 +215,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	qry->rtable = pstate->p_rtable;
+	qry->rteperminfos = pstate->p_rteperminfos;
 
 	/*
 	 * Transform the join condition.  This includes references to the target
@@ -287,7 +288,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 				{
 					List	   *exprList = NIL;
 					ListCell   *lc;
-					RangeTblEntry *rte;
+					RTEPermissionInfo *perminfo;
 					ListCell   *icols;
 					ListCell   *attnos;
 					List	   *icolumns;
@@ -346,7 +347,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 					 * of expressions. Also, mark all the target columns as
 					 * needing insert permissions.
 					 */
-					rte = pstate->p_target_nsitem->p_rte;
+					perminfo = pstate->p_target_nsitem->p_perminfo;
 					forthree(lc, exprList, icols, icolumns, attnos, attrnos)
 					{
 						Expr	   *expr = (Expr *) lfirst(lc);
@@ -360,8 +361,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 											  false);
 						action->targetList = lappend(action->targetList, tle);
 
-						rte->insertedCols =
-							bms_add_member(rte->insertedCols,
+						perminfo->insertedCols =
+							bms_add_member(perminfo->insertedCols,
 										   attr_num - FirstLowInvalidHeapAttributeNumber);
 					}
 				}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4665f0b2b7..a3addb111d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1037,11 +1037,15 @@ markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col)
 
 	if (rte->rtekind == RTE_RELATION)
 	{
+		RTEPermissionInfo *perminfo;
+
 		/* Make sure the rel as a whole is marked for SELECT access */
-		rte->requiredPerms |= ACL_SELECT;
+		perminfo = getRTEPermissionInfo(pstate->p_rteperminfos, rte);
+		perminfo->requiredPerms |= ACL_SELECT;
 		/* Must offset the attnum to fit in a bitmapset */
-		rte->selectedCols = bms_add_member(rte->selectedCols,
-										   col - FirstLowInvalidHeapAttributeNumber);
+		perminfo->selectedCols =
+			bms_add_member(perminfo->selectedCols,
+						   col - FirstLowInvalidHeapAttributeNumber);
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
@@ -1251,10 +1255,13 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
  *
  * rte: the new RangeTblEntry for the rel
  * rtindex: its index in the rangetable list
+ * perminfo: permission list entry for the rel
  * tupdesc: the physical column information
  */
 static ParseNamespaceItem *
-buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex,
+						 RTEPermissionInfo *perminfo,
+						 TupleDesc tupdesc)
 {
 	ParseNamespaceItem *nsitem;
 	ParseNamespaceColumn *nscolumns;
@@ -1290,6 +1297,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
 	nsitem->p_rtindex = rtindex;
+	nsitem->p_perminfo = perminfo;
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
 	nsitem->p_rel_visible = true;
@@ -1433,6 +1441,7 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
@@ -1469,7 +1478,7 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1478,12 +1487,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1497,7 +1502,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									  rel->rd_att);
+									  perminfo, rel->rd_att);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1534,6 +1539,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	RTEPermissionInfo *perminfo;
 	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
 
 	Assert(pstate != NULL);
@@ -1557,7 +1563,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags and initialize access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -1566,12 +1572,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = ACL_SELECT;
-	rte->checkAsUser = InvalidOid;	/* not set-uid by default, either */
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
 
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
@@ -1585,7 +1587,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * list --- caller must do that if appropriate.
 	 */
 	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
-									rel->rd_att);
+									perminfo, rel->rd_att);
 }
 
 /*
@@ -1659,21 +1661,15 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->eref = eref;
 
 	/*
-	 * Set flags and access permissions.
+	 * Set flags.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -1990,20 +1986,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by the RTE
-	 * permissions mechanism).
+	 * Functions are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2015,7 +2004,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -2082,20 +2071,13 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Tablefuncs are never checked for access rights (at least, not by the
-	 * RTE permissions mechanism).
+	 * Tablefuncs are never checked for access rights (at least, not by
+	 * ExecCheckPermissions()), so no need to perform AddRelPermissionsInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for tablefunc RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2170,19 +2152,13 @@ addRangeTableEntryForValues(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = lateral;
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2267,19 +2243,13 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Joins are never checked for access rights.
+	 * Joins are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2294,6 +2264,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
 	nsitem->p_names = rte->eref;
 	nsitem->p_rte = rte;
+	nsitem->p_perminfo = NULL;
 	nsitem->p_rtindex = list_length(pstate->p_rtable);
 	nsitem->p_nscolumns = nscolumns;
 	/* set default visibility flags; might get changed later */
@@ -2417,19 +2388,13 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * Subqueries are never checked for access rights.
+	 * Subqueries are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2543,16 +2508,13 @@ addRangeTableEntryForENR(ParseState *pstate,
 	/*
 	 * Set flags and access permissions.
 	 *
-	 * ENRs are never checked for access rights.
+	 * ENRs are never checked for access rights, so no need to perform
+	 * addRTEPermissionInfo().
 	 */
 	rte->lateral = false;
 	rte->inh = false;			/* never true for ENRs */
 	rte->inFromCl = inFromCl;
 
-	rte->requiredPerms = 0;
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-
 	/*
 	 * Add completed RTE to pstate's range table list, so that we know its
 	 * index.  But we don't add it to the join list --- caller must do that if
@@ -2564,7 +2526,7 @@ addRangeTableEntryForENR(ParseState *pstate,
 	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
 	 * list --- caller must do that if appropriate.
 	 */
-	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable), NULL,
 									tupdesc);
 }
 
@@ -3189,6 +3151,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 				  int sublevels_up, bool require_col_privs, int location)
 {
 	RangeTblEntry *rte = nsitem->p_rte;
+	RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 	List	   *names,
 			   *vars;
 	ListCell   *name,
@@ -3206,7 +3169,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 	 * relation of UPDATE/DELETE, which cannot be under a join.)
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		rte->requiredPerms |= ACL_SELECT;
+	{
+		Assert(perminfo != NULL);
+		perminfo->requiredPerms |= ACL_SELECT;
+	}
 
 	forboth(name, names, var, vars)
 	{
@@ -3855,3 +3821,57 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * addRTEPermissionInfo
+ *		Creates RTEPermissionInfo for a given RTE and adds it into the
+ *		provided list
+ *
+ * Returns the RTEPermissionInfo and sets rte->perminfoindex.
+ */
+RTEPermissionInfo *
+addRTEPermissionInfo(List **rteperminfos, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	Assert(rte->perminfoindex == 0);
+
+	/* Nope, so make one and add to the list. */
+	perminfo = makeNode(RTEPermissionInfo);
+	perminfo->relid = rte->relid;
+	perminfo->inh = rte->inh;
+	/* Other information is set by fetching the node as and where needed. */
+
+	*rteperminfos = lappend(*rteperminfos, perminfo);
+
+	/* Note its index (1-based!) */
+	rte->perminfoindex = list_length(*rteperminfos);
+
+	return perminfo;
+}
+
+/*
+ * getRTEPermissionInfo
+ *		Find RTEPermissionInfo for a given relation in the provided list
+ *
+ * This is a simple list_nth() operation though it's good to have the function
+ * for the various sanity checks.
+ */
+RTEPermissionInfo *
+getRTEPermissionInfo(List *rteperminfos, RangeTblEntry *rte)
+{
+	RTEPermissionInfo *perminfo;
+
+	if (rte->perminfoindex == 0 ||
+		rte->perminfoindex > list_length(rteperminfos))
+		elog(ERROR, "invalid perminfoindex %d in RTE with relid %u",
+			 rte->perminfoindex, rte->relid);
+	perminfo = list_nth_node(RTEPermissionInfo, rteperminfos,
+							 rte->perminfoindex - 1);
+	if (perminfo->relid != rte->relid)
+		elog(ERROR, "permission info at index %u (with relid=%u) does not match provided RTE (with relid=%u)",
+			 rte->perminfoindex, perminfo->relid, rte->relid);
+
+	return perminfo;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 8e0d6fd01f..56d64c8851 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1132,7 +1132,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 		 *
 		 * Note: this code is a lot like transformColumnRef; it's tempting to
 		 * call that instead and then replace the resulting whole-row Var with
-		 * a list of Vars.  However, that would leave us with the RTE's
+		 * a list of Vars.  However, that would leave us with the relation's
 		 * selectedCols bitmap showing the whole row as needing select
 		 * permission, as well as the individual columns.  That would be
 		 * incorrect (since columns added later shouldn't need select
@@ -1367,6 +1367,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 	else
 	{
 		RangeTblEntry *rte = nsitem->p_rte;
+		RTEPermissionInfo *perminfo = nsitem->p_perminfo;
 		List	   *vars;
 		ListCell   *l;
 
@@ -1381,7 +1382,10 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		 * target relation of UPDATE/DELETE, which cannot be under a join.)
 		 */
 		if (rte->rtekind == RTE_RELATION)
-			rte->requiredPerms |= ACL_SELECT;
+		{
+			Assert(perminfo != NULL);
+			perminfo->requiredPerms |= ACL_SELECT;
+		}
 
 		/* Require read access to each column */
 		foreach(l, vars)
@@ -1414,11 +1418,11 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
 	 * as simple Vars.  Note: if the RTE is a relation, this case leaves us
-	 * with the RTE's selectedCols bitmap showing the whole row as needing
-	 * select permission, as well as the individual columns.  However, we can
-	 * only get here for weird notations like (table.*).*, so it's not worth
-	 * trying to clean up --- arguably, the permissions marking is correct
-	 * anyway for such cases.
+	 * with its RTEPermissionInfo's selectedCols bitmap showing the whole row
+	 * as needing select permission, as well as the individual columns.
+	 * However, we can only get here for weird notations like (table.*).*, so
+	 * it's not worth trying to clean up --- arguably, the permissions marking
+	 * is correct anyway for such cases.
 	 */
 	if (IsA(expr, Var) &&
 		((Var *) expr)->varattno == InvalidAttrNumber)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 36791d8817..342a179133 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3023,9 +3023,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 											  AccessShareLock,
 											  makeAlias("new", NIL),
 											  false, false);
-	/* Must override addRangeTableEntry's default access-check flags */
-	oldnsitem->p_rte->requiredPerms = 0;
-	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -3081,6 +3078,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 		nothing_qry->commandType = CMD_NOTHING;
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->rteperminfos = pstate->p_rteperminfos;
 		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */
 
 		*actions = list_make1(nothing_qry);
@@ -3123,8 +3121,6 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 													  AccessShareLock,
 													  makeAlias("new", NIL),
 													  false, false);
-			oldnsitem->p_rte->requiredPerms = 0;
-			newnsitem->p_rte->requiredPerms = 0;
 			addNSItemToQuery(sub_pstate, oldnsitem, false, true, false);
 			addNSItemToQuery(sub_pstate, newnsitem, false, true, false);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index f9efe6c4c6..96772e4d73 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -156,6 +156,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
@@ -516,6 +517,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	addRTEPermissionInfo(&estate->es_rteperminfos, rte);
+
 	edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
 	/*
@@ -1813,6 +1816,7 @@ apply_handle_update(StringInfo s)
 	bool		has_oldtup;
 	TupleTableSlot *remoteslot;
 	RangeTblEntry *target_rte;
+	RTEPermissionInfo *target_perminfo;
 	MemoryContext oldctx;
 
 	/*
@@ -1861,6 +1865,7 @@ apply_handle_update(StringInfo s)
 	 * on the subscriber, since we are not touching those.
 	 */
 	target_rte = list_nth(estate->es_range_table, 0);
+	target_perminfo = list_nth(estate->es_rteperminfos, 0);
 	for (int i = 0; i < remoteslot->tts_tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att = TupleDescAttr(remoteslot->tts_tupleDescriptor, i);
@@ -1870,14 +1875,14 @@ apply_handle_update(StringInfo s)
 		{
 			Assert(remoteattnum < newtup.ncols);
 			if (newtup.colstatus[remoteattnum] != LOGICALREP_COLUMN_UNCHANGED)
-				target_rte->updatedCols =
-					bms_add_member(target_rte->updatedCols,
+				target_perminfo->updatedCols =
+					bms_add_member(target_perminfo->updatedCols,
 								   i + 1 - FirstLowInvalidHeapAttributeNumber);
 		}
 	}
 
 	/* Also populate extraUpdatedCols, in case we have generated columns */
-	fill_extraUpdatedCols(target_rte, rel->localrel);
+	fill_extraUpdatedCols(target_rte, target_perminfo, rel->localrel);
 
 	/* Build the search tuple. */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 8ac2c81b06..9f3afe965a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -632,14 +632,14 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 /*
  * setRuleCheckAsUser
  *		Recursively scan a query or expression tree and set the checkAsUser
- *		field to the given userid in all rtable entries.
+ *		field to the given userid in all RTEPermissionInfos of the query.
  *
  * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
- * RTE entry will be overridden when the view rule is expanded, and the
- * checkAsUser field of the NEW entry is irrelevant because that entry's
- * requiredPerms bits will always be zero.  However, for other types of rules
- * it's important to set these fields to match the rule owner.  So we just set
- * them always.
+ * RTE entry's RTEPermissionInfo will be overridden when the view rule is
+ * expanded, and the checkAsUser for the NEW RTE entry's RTEPermissionInfo is
+ * irrelevant because its requiredPerms bits will always be zero.  However, for
+ * other types of rules it's important to set these fields to match the rule
+ * owner.  So we just set them always.
  */
 void
 setRuleCheckAsUser(Node *node, Oid userid)
@@ -666,18 +666,21 @@ setRuleCheckAsUser_Query(Query *qry, Oid userid)
 {
 	ListCell   *l;
 
-	/* Set all the RTEs in this query node */
+	/* Set in all RTEPermissionInfos for this query. */
+	foreach(l, qry->rteperminfos)
+	{
+		RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
+
+		perminfo->checkAsUser = userid;
+	}
+
+	/* Now recurse to any subquery RTEs */
 	foreach(l, qry->rtable)
 	{
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
-		{
-			/* Recurse into subquery in FROM */
 			setRuleCheckAsUser_Query(rte->subquery, userid);
-		}
-		else
-			rte->checkAsUser = userid;
 	}
 
 	/* Recurse into subquery-in-WITH */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 1e3efbbd36..19f13effba 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -353,6 +353,7 @@ rewriteRuleAction(Query *parsetree,
 	Query	   *sub_action;
 	Query	  **sub_action_ptr;
 	acquireLocksOnSubLinks_context context;
+	List	   *action_rteperminfos;
 
 	context.for_execute = true;
 
@@ -395,36 +396,39 @@ rewriteRuleAction(Query *parsetree,
 	 * Generate expanded rtable consisting of main parsetree's rtable plus
 	 * rule action's rtable; this becomes the complete rtable for the rule
 	 * action.  Some of the entries may be unused after we finish rewriting,
-	 * but we leave them all in place for two reasons:
+	 * but we leave them all in place to avoid having to adjust the query's
+	 * varnos.  RT entries that are not referenced in the completed jointree
+	 * will be ignored by the planner, so they do not affect query semantics.
 	 *
-	 * We'd have a much harder job to adjust the query's varnos if we
-	 * selectively removed RT entries.
+	 * Also merge RTEPermissionInfo lists to ensure that all permissions are
+	 * checked correctly.
 	 *
 	 * If the rule is INSTEAD, then the original query won't be executed at
-	 * all, and so its rtable must be preserved so that the executor will do
-	 * the correct permissions checks on it.
+	 * all, and so its rteperminfos must be preserved so that the executor will
+	 * do the correct permissions checks on the relations referenced in it.
+	 * This allows us to check that the caller has, say, insert-permission on
+	 * a view, when the view is not semantically referenced at all in the
+	 * resulting query.
 	 *
-	 * RT entries that are not referenced in the completed jointree will be
-	 * ignored by the planner, so they do not affect query semantics.  But any
-	 * permissions checks specified in them will be applied during executor
-	 * startup (see ExecCheckRTEPerms()).  This allows us to check that the
-	 * caller has, say, insert-permission on a view, when the view is not
-	 * semantically referenced at all in the resulting query.
-	 *
-	 * When a rule is not INSTEAD, the permissions checks done on its copied
-	 * RT entries will be redundant with those done during execution of the
-	 * original query, but we don't bother to treat that case differently.
-	 *
-	 * NOTE: because planner will destructively alter rtable, we must ensure
-	 * that rule action's rtable is separate and shares no substructure with
-	 * the main rtable.  Hence do a deep copy here.
+	 * When a rule is not INSTEAD, the permissions checks done using the
+	 * copied entries will be redundant with those done during execution of
+	 * the original query, but we don't bother to treat that case differently.
 	 *
 	 * Note also that RewriteQuery() relies on the fact that RT entries from
 	 * the original query appear at the start of the expanded rtable, so
 	 * beware of changing this.
+	 *
+	 * NOTE: because planner will destructively alter rtable and rteperminfos,
+	 * we must ensure that rule action's lists are separate and shares no
+	 * substructure with the main query's lists.  Hence do a deep copy here
+	 * for both.
 	 */
-	sub_action->rtable = list_concat(copyObject(parsetree->rtable),
-									 sub_action->rtable);
+	action_rteperminfos = sub_action->rteperminfos;
+	sub_action->rteperminfos = copyObject(parsetree->rteperminfos);
+	sub_action->rtable = CombineRangeTables(copyObject(parsetree->rtable),
+											sub_action->rtable,
+											action_rteperminfos,
+											&sub_action->rteperminfos);
 
 	/*
 	 * There could have been some SubLinks in parsetree's rtable, in which
@@ -1628,10 +1632,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * Record in target_rte->extraUpdatedCols the indexes of any generated columns
- * that depend on any columns mentioned in target_rte->updatedCols.
+ * columns that depend on any columns mentioned in
+ * target_perminfo->updatedCols.
  */
 void
-fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
+fill_extraUpdatedCols(RangeTblEntry *target_rte,
+					  RTEPermissionInfo *target_perminfo,
+					  Relation target_relation)
 {
 	TupleDesc	tupdesc = RelationGetDescr(target_relation);
 	TupleConstr *constr = tupdesc->constr;
@@ -1654,7 +1661,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 			expr = stringToNode(defval->adbin);
 			pull_varattnos(expr, 1, &attrs_used);
 
-			if (bms_overlap(target_rte->updatedCols, attrs_used))
+			if (bms_overlap(target_perminfo->updatedCols, attrs_used))
 				target_rte->extraUpdatedCols =
 					bms_add_member(target_rte->extraUpdatedCols,
 								   defval->adnum - FirstLowInvalidHeapAttributeNumber);
@@ -1747,6 +1754,8 @@ ApplyRetrieveRule(Query *parsetree,
 	Query	   *rule_action;
 	RangeTblEntry *rte,
 			   *subrte;
+	RTEPermissionInfo *perminfo,
+			   *sub_perminfo;
 	RowMarkClause *rc;
 
 	if (list_length(rule->actions) != 1)
@@ -1787,18 +1796,6 @@ ApplyRetrieveRule(Query *parsetree,
 			parsetree->rtable = lappend(parsetree->rtable, newrte);
 			parsetree->resultRelation = list_length(parsetree->rtable);
 
-			/*
-			 * There's no need to do permissions checks twice, so wipe out the
-			 * permissions info for the original RTE (we prefer to keep the
-			 * bits set on the result RTE).
-			 */
-			rte->requiredPerms = 0;
-			rte->checkAsUser = InvalidOid;
-			rte->selectedCols = NULL;
-			rte->insertedCols = NULL;
-			rte->updatedCols = NULL;
-			rte->extraUpdatedCols = NULL;
-
 			/*
 			 * For the most part, Vars referencing the view should remain as
 			 * they are, meaning that they implicitly represent OLD values.
@@ -1862,12 +1859,6 @@ ApplyRetrieveRule(Query *parsetree,
 
 	/*
 	 * Recursively expand any view references inside the view.
-	 *
-	 * Note: this must happen after markQueryForLocking.  That way, any UPDATE
-	 * permission bits needed for sub-views are initially applied to their
-	 * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their
-	 * OLD rangetable entries by the action below (in a recursive call of this
-	 * routine).
 	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
@@ -1876,6 +1867,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 * original RTE to a subquery RTE.
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
+	perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rte);
 
 	rte->rtekind = RTE_SUBQUERY;
 	rte->subquery = rule_action;
@@ -1885,6 +1877,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->relkind = 0;
 	rte->rellockmode = 0;
 	rte->tablesample = NULL;
+	rte->perminfoindex = 0;		/* no permission checking for this RTE */
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
@@ -1893,19 +1886,12 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->requiredPerms = rte->requiredPerms;
-	subrte->checkAsUser = rte->checkAsUser;
-	subrte->selectedCols = rte->selectedCols;
-	subrte->insertedCols = rte->insertedCols;
-	subrte->updatedCols = rte->updatedCols;
-	subrte->extraUpdatedCols = rte->extraUpdatedCols;
-
-	rte->requiredPerms = 0;		/* no permission check on subquery itself */
-	rte->checkAsUser = InvalidOid;
-	rte->selectedCols = NULL;
-	rte->insertedCols = NULL;
-	rte->updatedCols = NULL;
-	rte->extraUpdatedCols = NULL;
+	sub_perminfo = getRTEPermissionInfo(rule_action->rteperminfos, subrte);
+	sub_perminfo->requiredPerms = perminfo->requiredPerms;
+	sub_perminfo->checkAsUser = perminfo->checkAsUser;
+	sub_perminfo->selectedCols = perminfo->selectedCols;
+	sub_perminfo->insertedCols = perminfo->insertedCols;
+	sub_perminfo->updatedCols = perminfo->updatedCols;
 
 	return parsetree;
 }
@@ -1935,8 +1921,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
+			RTEPermissionInfo *perminfo;
+
 			applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+
+			perminfo = getRTEPermissionInfo(qry->rteperminfos, rte);
+			perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
@@ -3077,6 +3067,9 @@ rewriteTargetView(Query *parsetree, Relation view)
 	RangeTblEntry *base_rte;
 	RangeTblEntry *view_rte;
 	RangeTblEntry *new_rte;
+	RTEPermissionInfo *base_perminfo;
+	RTEPermissionInfo *view_perminfo;
+	RTEPermissionInfo *new_perminfo;
 	Relation	base_rel;
 	List	   *view_targetlist;
 	ListCell   *lc;
@@ -3213,6 +3206,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	base_rt_index = rtr->rtindex;
 	base_rte = rt_fetch(base_rt_index, viewquery->rtable);
 	Assert(base_rte->rtekind == RTE_RELATION);
+	base_perminfo = getRTEPermissionInfo(viewquery->rteperminfos, base_rte);
 
 	/*
 	 * Up to now, the base relation hasn't been touched at all in our query.
@@ -3284,57 +3278,68 @@ rewriteTargetView(Query *parsetree, Relation view)
 				   0);
 
 	/*
-	 * If the view has "security_invoker" set, mark the new target RTE for the
-	 * permissions checks that we want to enforce against the query caller.
-	 * Otherwise we want to enforce them against the view owner.
+	 * If the view has "security_invoker" set, mark the new target relation
+	 * for the permissions checks that we want to enforce against the query
+	 * caller. Otherwise we want to enforce them against the view owner.
 	 *
 	 * At the relation level, require the same INSERT/UPDATE/DELETE
 	 * permissions that the query caller needs against the view.  We drop the
-	 * ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
+	 * ACL_SELECT bit that is presumably in new_perminfo->requiredPerms
+	 * initially.
 	 *
-	 * Note: the original view RTE remains in the query's rangetable list.
-	 * Although it will be unused in the query plan, we need it there so that
-	 * the executor still performs appropriate permissions checks for the
-	 * query caller's use of the view.
+	 * Note: the original view's RTEPermissionInfo remains in the query's
+	 * rteperminfos so that the executor still performs appropriate permissions
+	 * checks for the query caller's use of the view.
 	 */
+	view_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, view_rte);
+
+	/*
+	 * Disregard the perminfo in viewquery->rteperminfos that the base_rte
+	 * would currently be pointing at, because we'd like it to point now
+	 * to a new one that will be filled below.  Must set perminfoindex to
+	 * 0 to not trip over the Assert in addRTEPermissionInfo().
+	 */
+	new_rte->perminfoindex = 0;
+	new_perminfo = addRTEPermissionInfo(&parsetree->rteperminfos, new_rte);
 	if (RelationHasSecurityInvoker(view))
-		new_rte->checkAsUser = InvalidOid;
+		new_perminfo->checkAsUser = InvalidOid;
 	else
-		new_rte->checkAsUser = view->rd_rel->relowner;
-
-	new_rte->requiredPerms = view_rte->requiredPerms;
+		new_perminfo->checkAsUser = view->rd_rel->relowner;
+	new_perminfo->requiredPerms = view_perminfo->requiredPerms;
 
 	/*
 	 * Now for the per-column permissions bits.
 	 *
-	 * Initially, new_rte contains selectedCols permission check bits for all
-	 * base-rel columns referenced by the view, but since the view is a SELECT
-	 * query its insertedCols/updatedCols is empty.  We set insertedCols and
-	 * updatedCols to include all the columns the outer query is trying to
-	 * modify, adjusting the column numbers as needed.  But we leave
-	 * selectedCols as-is, so the view owner must have read permission for all
-	 * columns used in the view definition, even if some of them are not read
-	 * by the outer query.  We could try to limit selectedCols to only columns
-	 * used in the transformed query, but that does not correspond to what
-	 * happens in ordinary SELECT usage of a view: all referenced columns must
-	 * have read permission, even if optimization finds that some of them can
-	 * be discarded during query transformation.  The flattening we're doing
-	 * here is an optional optimization, too.  (If you are unpersuaded and
-	 * want to change this, note that applying adjust_view_column_set to
-	 * view_rte->selectedCols is clearly *not* the right answer, since that
-	 * neglects base-rel columns used in the view's WHERE quals.)
+	 * Initially, new_perminfo (base_perminfo) contains selectedCols permission
+	 * check bits for all base-rel columns referenced by the view, but since
+	 * the view is a SELECT query its insertedCols/updatedCols is empty.  We
+	 * set insertedCols and updatedCols to include all the columns the outer
+	 * query is trying to modify, adjusting the column numbers as needed.  But
+	 * we leave selectedCols as-is, so the view owner must have read permission
+	 * for all columns used in the view definition, even if some of them are
+	 * not read by the outer query.  We could try to limit selectedCols to only
+	 * columns used in the transformed query, but that does not correspond to
+	 * what happens in ordinary SELECT usage of a view: all referenced columns
+	 * must have read permission, even if optimization finds that some of them
+	 * can be discarded during query transformation.  The flattening we're
+	 * doing here is an optional optimization, too.  (If you are unpersuaded
+	 * and want to change this, note that applying adjust_view_column_set to
+	 * view_perminfo->selectedCols is clearly *not* the right answer, since
+	 * that neglects base-rel columns used in the view's WHERE quals.)
 	 *
 	 * This step needs the modified view targetlist, so we have to do things
 	 * in this order.
 	 */
-	Assert(bms_is_empty(new_rte->insertedCols) &&
-		   bms_is_empty(new_rte->updatedCols));
+	Assert(bms_is_empty(new_perminfo->insertedCols) &&
+		   bms_is_empty(new_perminfo->updatedCols));
+
+	new_perminfo->selectedCols = base_perminfo->selectedCols;
 
-	new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols,
-												   view_targetlist);
+	new_perminfo->insertedCols =
+		adjust_view_column_set(view_perminfo->insertedCols, view_targetlist);
 
-	new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols,
-												  view_targetlist);
+	new_perminfo->updatedCols =
+		adjust_view_column_set(view_perminfo->updatedCols, view_targetlist);
 
 	/*
 	 * Move any security barrier quals from the view RTE onto the new target
@@ -3438,7 +3443,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 * from the view, hence we need a new column alias list).  This should
 		 * match transformOnConflictClause.  In particular, note that the
 		 * relkind is set to composite to signal that we're not dealing with
-		 * an actual relation, and no permissions checks are wanted.
+		 * an actual relation.
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
@@ -3449,8 +3454,8 @@ rewriteTargetView(Query *parsetree, Relation view)
 													   false, false);
 		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
-		new_exclRte->requiredPerms = 0;
-		/* other permissions fields in new_exclRte are already empty */
+		/* Ignore the RTEPermissionInfo that would've been added. */
+		new_exclRte->perminfoindex = 0;
 
 		parsetree->rtable = lappend(parsetree->rtable, new_exclRte);
 		new_exclRelIndex = parsetree->onConflict->exclRelIndex =
@@ -3728,6 +3733,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
+		RTEPermissionInfo *rt_perminfo;
 		Relation	rt_entry_relation;
 		List	   *locks;
 		int			product_orig_rt_length;
@@ -3740,6 +3746,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
 		Assert(rt_entry->rtekind == RTE_RELATION);
+		rt_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rt_entry);
 
 		/*
 		 * We can use NoLock here since either the parser or
@@ -3833,7 +3840,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
 									NULL, 0, NULL);
 
 			/* Also populate extraUpdatedCols (for generated columns) */
-			fill_extraUpdatedCols(rt_entry, rt_entry_relation);
+			fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation);
 		}
 		else if (event == CMD_MERGE)
 		{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 101c39553a..bf8bbbacc1 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -316,6 +316,39 @@ contains_multiexpr_param(Node *node, void *context)
 	return expression_tree_walker(node, contains_multiexpr_param, context);
 }
 
+/*
+ * CombineRangeTables
+ * 		Adds the RTEs of 'rtable2' into 'rtable1'
+ *
+ * This also adds the RTEPermissionInfos of 'rteperminfos2' (belonging to the
+ * RTEs in 'rtable2') into *rteperminfos1 and also updates perminfoindex of the
+ * RTEs in 'rtable2' to now point to the perminfos' indexes in *rteperminfos1.
+ *
+ * Note that this changes both 'rtable1' and 'rteperminfos1' destructively, so
+ * the caller should have better passed safe-to-modify copies.
+ */
+List *
+CombineRangeTables(List *rtable1, List *rtable2,
+				   List *rteperminfos2, List **rteperminfos1)
+{
+	ListCell   *l;
+	int			offset = list_length(*rteperminfos1);
+
+	if (offset > 0)
+	{
+		foreach(l, rtable2)
+		{
+			RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+			if (rte->perminfoindex > 0)
+				rte->perminfoindex += offset;
+		}
+	}
+
+	*rteperminfos1 = list_concat(*rteperminfos1, rteperminfos2);
+
+	return list_concat(rtable1, rtable2);
+}
 
 /*
  * OffsetVarNodes - adjust Vars when appending one query's RT to another
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index f49cfb6cc6..f03b36a6e4 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -47,6 +47,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/plannodes.h"
 #include "parser/parsetree.h"
+#include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -115,6 +116,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	CmdType		commandType;
 	List	   *permissive_policies;
 	List	   *restrictive_policies;
+	RTEPermissionInfo *perminfo;
 
 	/* Defaults for the return values */
 	*securityQuals = NIL;
@@ -122,16 +124,21 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasRowSecurity = false;
 	*hasSubLinks = false;
 
+	Assert(rte->rtekind == RTE_RELATION);
+
 	/* If this is not a normal relation, just return immediately */
 	if (rte->relkind != RELKIND_RELATION &&
 		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
+	perminfo = getRTEPermissionInfo(root->rteperminfos, rte);
+
 	/* Switch to checkAsUser if it's set */
-	user_id = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
+	user_id = OidIsValid(perminfo->checkAsUser) ?
+		perminfo->checkAsUser : GetUserId();
 
 	/* Determine the state of RLS for this, pass checkAsUser explicitly */
-	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
+	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);
 
 	/* If there is no RLS on this table at all, nothing to do */
 	if (rls_status == RLS_NONE)
@@ -196,7 +203,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * which the user does not have access to via the UPDATE USING policies,
 	 * similar to how we require normal UPDATE rights for these queries.
 	 */
-	if (commandType == CMD_SELECT && rte->requiredPerms & ACL_UPDATE)
+	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
 	{
 		List	   *update_permissive_policies;
 		List	   *update_restrictive_policies;
@@ -243,7 +250,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 */
 	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
 		 commandType == CMD_MERGE) &&
-		rte->requiredPerms & ACL_SELECT)
+		perminfo->requiredPerms & ACL_SELECT)
 	{
 		List	   *select_permissive_policies;
 		List	   *select_restrictive_policies;
@@ -286,7 +293,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		 * raised if a policy is violated; otherwise, we might end up silently
 		 * dropping rows to be added.
 		 */
-		if (rte->requiredPerms & ACL_SELECT)
+		if (perminfo->requiredPerms & ACL_SELECT)
 		{
 			List	   *select_permissive_policies = NIL;
 			List	   *select_restrictive_policies = NIL;
@@ -342,7 +349,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * for this relation, also as WCO policies, again, to avoid
 			 * silently dropping data.  See above.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 			{
 				get_policies_for_relation(rel, CMD_SELECT, user_id,
 										  &conflict_select_permissive_policies,
@@ -371,7 +378,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
 			 * are required for this relation.
 			 */
-			if (rte->requiredPerms & ACL_SELECT)
+			if (perminfo->requiredPerms & ACL_SELECT)
 				add_with_check_options(rel, rt_index,
 									   WCO_RLS_UPDATE_CHECK,
 									   conflict_select_permissive_policies,
@@ -474,8 +481,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
 	 * in case they contain any subqueries referring to other relations.
 	 */
-	setRuleCheckAsUser((Node *) *securityQuals, rte->checkAsUser);
-	setRuleCheckAsUser((Node *) *withCheckOptions, rte->checkAsUser);
+	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
+	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
 
 	/*
 	 * Mark this query as having row security, so plancache can invalidate it
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc07157037..29f29d664b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -1375,6 +1375,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
 	RangeTblEntry *fkrte;
+	RTEPermissionInfo *pk_perminfo;
+	RTEPermissionInfo *fk_perminfo;
 	const char *sep;
 	const char *fk_only;
 	const char *pk_only;
@@ -1397,27 +1399,34 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	pkrte->relid = RelationGetRelid(pk_rel);
 	pkrte->relkind = pk_rel->rd_rel->relkind;
 	pkrte->rellockmode = AccessShareLock;
-	pkrte->requiredPerms = ACL_SELECT;
+
+	pk_perminfo = makeNode(RTEPermissionInfo);
+	pk_perminfo->relid = RelationGetRelid(pk_rel);
+	pk_perminfo->requiredPerms = ACL_SELECT;
 
 	fkrte = makeNode(RangeTblEntry);
 	fkrte->rtekind = RTE_RELATION;
 	fkrte->relid = RelationGetRelid(fk_rel);
 	fkrte->relkind = fk_rel->rd_rel->relkind;
 	fkrte->rellockmode = AccessShareLock;
-	fkrte->requiredPerms = ACL_SELECT;
+
+	fk_perminfo = makeNode(RTEPermissionInfo);
+	fk_perminfo->relid = RelationGetRelid(fk_rel);
+	fk_perminfo->requiredPerms = ACL_SELECT;
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
 		int			attno;
 
 		attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
+		pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
 
 		attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
-		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
+		fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
 	}
 
-	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
+	if (!ExecCheckPermissions(list_make2(fkrte, pkrte),
+							  list_make2(fk_perminfo, pk_perminfo), false))
 		return false;
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a50eecc7c8..450e5124a5 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,8 +847,8 @@ RelationBuildRuleLock(Relation relation)
 
 		/*
 		 * Scan through the rule's actions and set the checkAsUser field on
-		 * all rtable entries. We have to look at the qual as well, in case it
-		 * contains sublinks.
+		 * all RTEPermissionInfos. We have to look at the qual as well, in
+		 * case it contains sublinks.
 		 *
 		 * The reason for doing this when the rule is loaded, rather than when
 		 * it is stored, is that otherwise ALTER TABLE OWNER would have to
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 8d9cc5accd..c5e5875eb8 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -97,7 +97,8 @@ typedef struct CopyFromStateData
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rteperminfos;	/* single element list of RTEPermissionInfo */
 	ExprState  *qualexpr;
 
 	TransitionCaptureState *transition_capture;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 32bbbc5927..98ee6876b6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -80,9 +80,9 @@ extern PGDLLIMPORT ExecutorFinish_hook_type ExecutorFinish_hook;
 typedef void (*ExecutorEnd_hook_type) (QueryDesc *queryDesc);
 extern PGDLLIMPORT ExecutorEnd_hook_type ExecutorEnd_hook;
 
-/* Hook for plugins to get control in ExecCheckRTPerms() */
-typedef bool (*ExecutorCheckPerms_hook_type) (List *, bool);
-extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPermissions_hook_type) (List *, List *, bool);
+extern PGDLLIMPORT ExecutorCheckPermissions_hook_type ExecutorCheckPermissions_hook;
 
 
 /*
@@ -199,7 +199,8 @@ extern void standard_ExecutorFinish(QueryDesc *queryDesc);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
+extern bool ExecCheckPermissions(List *rangeTable,
+								 List *rteperminfos, bool ereport_on_violation);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
@@ -579,6 +580,7 @@ exec_rt_fetch(Index rti, EState *estate)
 }
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
+extern RTEPermissionInfo *ExecgetRTEPermissionInfo(RangeTblEntry *rte, EState *estate);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 								   Index rti);
 
@@ -605,6 +607,7 @@ extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relIn
 extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
 extern TupleConversionMap *ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate);
 
+extern Oid	ExecGetResultRelCheckAsUser(ResultRelInfo *relInfo, EState *estate);
 extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate);
 extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9c6e8f5e13..75d13283b2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -617,6 +617,7 @@ typedef struct EState
 								 * pointers, or NULL if not yet opened */
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
+	List	   *es_rteperminfos; /* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	List		*es_part_prune_infos;	/* PlannedStmt.partPruneInfos */
 	List		*es_part_prune_results; /* QueryDesc.part_prune_results */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f17846e30e..6a6d3293e4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -154,6 +154,8 @@ typedef struct Query
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *rteperminfos;	/* list of RTEPermissionInfo nodes for the
+								 * rtable entries having perminfoindex > 0 */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses);
 								 * also USING clause for MERGE */
 
@@ -968,37 +970,6 @@ typedef struct PartitionCmd
  *	  control visibility.  But it is needed by ruleutils.c to determine
  *	  whether RTEs should be shown in decompiled queries.
  *
- *	  requiredPerms and checkAsUser specify run-time access permissions
- *	  checks to be performed at query startup.  The user must have *all*
- *	  of the permissions that are OR'd together in requiredPerms (zero
- *	  indicates no permissions checking).  If checkAsUser is not zero,
- *	  then do the permissions checks using the access rights of that user,
- *	  not the current effective user ID.  (This allows rules to act as
- *	  setuid gateways.)  Permissions checks only apply to RELATION RTEs.
- *
- *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
- *	  table-wide permissions then it is sufficient to have the permissions
- *	  on all columns identified in selectedCols (for SELECT) and/or
- *	  insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
- *	  have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
- *	  which cannot have negative integer members, so we subtract
- *	  FirstLowInvalidHeapAttributeNumber from column numbers before storing
- *	  them in these fields.  A whole-row Var reference is represented by
- *	  setting the bit for InvalidAttrNumber.
- *
- *	  updatedCols is also used in some other places, for example, to determine
- *	  which triggers to fire and in FDWs to know which changed columns they
- *	  need to ship off.
- *
- *	  Generated columns that are caused to be updated by an update to a base
- *	  column are listed in extraUpdatedCols.  This is not considered for
- *	  permission checking, but it is useful in those places that want to know
- *	  the full set of columns being updated as opposed to only the ones the
- *	  user explicitly mentioned in the query.  (There is currently no need for
- *	  an extraInsertedCols, but it could exist.)  Note that extraUpdatedCols
- *	  is populated during query rewrite, NOT in the parser, since generated
- *	  columns could be added after a rule has been parsed and stored.
- *
  *	  securityQuals is a list of security barrier quals (boolean expressions),
  *	  to be tested in the listed order before returning a row from the
  *	  relation.  It is always NIL in parser output.  Entries are added by the
@@ -1054,11 +1025,16 @@ typedef struct RangeTblEntry
 	 * current query; this happens if a DO ALSO rule simply scans the original
 	 * target table.  We leave such RTEs with their original lockmode so as to
 	 * avoid getting an additional, lesser lock.
+	 *
+	 * perminfoindex is 1-based index of the RTEPermissionInfo belonging to
+	 * this RTE in the containing struct's list of same; 0 if permissions need
+	 * not be checked for this RTE.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	Index		perminfoindex;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1178,14 +1154,64 @@ typedef struct RangeTblEntry
 	bool		lateral;		/* subquery, function, or values is LATERAL? */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause? */
+	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
+	List	   *securityQuals;	/* security barrier quals to apply, if any */
+} RangeTblEntry;
+
+/*
+ * RTEPermissionInfo
+ * 		Per-relation information for permission checking. Added to the Query
+ * 		node by the parser when adding the corresponding RTE to the query
+ * 		range table and subsequently editorialized on by the rewriter if
+ * 		needed after rule expansion.
+ *
+ * Only the relations directly mentioned in the query are checked for
+ * accesss permissions by the core executor, so only their RTEPermissionInfos
+ * are present in the Query.  However, extensions may want to check inheritance
+ * children too, depending on the value of rte->inh, so it's copied in 'inh'
+ * for their perusal.
+ *
+ * requiredPerms and checkAsUser specify run-time access permissions checks
+ * to be performed at query startup.  The user must have *all* of the
+ * permissions that are OR'd together in requiredPerms (never 0!).  If
+ * checkAsUser is not zero, then do the permissions checks using the access
+ * rights of that user, not the current effective user ID.  (This allows rules
+ * to act as setuid gateways.)
+ *
+ * For SELECT/INSERT/UPDATE permissions, if the user doesn't have table-wide
+ * permissions then it is sufficient to have the permissions on all columns
+ * identified in selectedCols (for SELECT) and/or insertedCols and/or
+ * updatedCols (INSERT with ON CONFLICT DO UPDATE may have all 3).
+ * selectedCols, insertedCols and updatedCols are bitmapsets, which cannot have
+ * negative integer members, so we subtract FirstLowInvalidHeapAttributeNumber
+ * from column numbers before storing them in these fields.  A whole-row Var
+ * reference is represented by setting the bit for InvalidAttrNumber.
+ *
+ * updatedCols is also used in some other places, for example, to determine
+ * which triggers to fire and in FDWs to know which changed columns they need
+ * to ship off.
+ *
+ * Generated columns that are caused to be updated by an update to a base
+ * column are listed in extraUpdatedCols.  This is not considered for
+ * permission checking, but it is useful in those places that want to know the
+ * full set of columns being updated as opposed to only the ones the user
+ * explicitly mentioned in the query.  (There is currently no need for an
+ * extraInsertedCols, but it could exist.)  Note that extraUpdatedCols is
+ * populated during query rewrite, NOT in the parser, since generated columns
+ * could be added after a rule has been parsed and stored.
+ */
+typedef struct RTEPermissionInfo
+{
+	NodeTag		type;
+
+	Oid			relid;			/* relation OID */
+	bool		inh;			/* separately check inheritance children? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
 	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
 	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
 	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
-	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
-	List	   *securityQuals;	/* security barrier quals to apply, if any */
-} RangeTblEntry;
+} RTEPermissionInfo;
 
 /*
  * RangeTblFunction -
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e0e5c15b09..cc04149f60 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -113,6 +113,9 @@ typedef struct PlannerGlobal
 	/* "flat" rangetable for executor */
 	List	   *finalrtable;
 
+	/* "flat" list of RTEPermissionInfos */
+	List	   *finalrteperminfos;
+
 	/* "flat" list of PlanRowMarks */
 	List	   *finalrowmarks;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ed664c5469..b780741686 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -83,6 +83,8 @@ typedef struct PlannedStmt
 								 * indexes of range table entries of the leaf
 								 * partitions scanned by prunable subplans;
 								 * see AcquireExecutorLocks() */
+	List	   *permInfos;		/* list of RTEPermissionInfo nodes for rtable
+								 * entries needing one */
 
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE/MERGE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h
index adcb1d7372..8ebd42b757 100644
--- a/src/include/optimizer/inherit.h
+++ b/src/include/optimizer/inherit.h
@@ -20,6 +20,8 @@
 extern void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 									 RangeTblEntry *rte, Index rti);
 
+extern Bitmapset *get_rel_all_updated_cols(PlannerInfo *root, RelOptInfo *rel);
+
 extern bool apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								  RelOptInfo *childrel, RangeTblEntry *childRTE,
 								  AppendRelInfo *appinfo);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de..ea84684acc 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -111,6 +111,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_rteperminfos: list of RTEPermissionInfo containing an entry corresponding
+ * to each RTE_RELATION entry in p_rtable.
+ *
  * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
  * This is one-for-one with p_rtable, but contains NULLs for non-join
  * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
@@ -181,6 +184,8 @@ struct ParseState
 	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_rteperminfos;	/* list of RTEPermissionInfo nodes for each
+								 * RTE_RELATION entry in rtable */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
@@ -234,7 +239,8 @@ struct ParseState
  * join's first N columns, the net effect is just that we expose only those
  * join columns via this nsitem.)
  *
- * p_rte and p_rtindex link to the underlying rangetable entry.
+ * p_rte and p_rtindex link to the underlying rangetable entry, and
+ * p_perminfo to the entry in rteperminfos.
  *
  * The p_nscolumns array contains info showing how to construct Vars
  * referencing the names appearing in the p_names->colnames list.
@@ -271,6 +277,7 @@ struct ParseNamespaceItem
 	Alias	   *p_names;		/* Table and column names */
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	RTEPermissionInfo *p_perminfo;	/* The relation's rteperminfos entry */
 	/* array of same length as p_names->colnames: */
 	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 484db165db..2f8d417709 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -99,6 +99,10 @@ extern ParseNamespaceItem *addRangeTableEntryForCTE(ParseState *pstate,
 extern ParseNamespaceItem *addRangeTableEntryForENR(ParseState *pstate,
 													RangeVar *rv,
 													bool inFromCl);
+extern RTEPermissionInfo *addRTEPermissionInfo(List **rteperminfos,
+											   RangeTblEntry *rte);
+extern RTEPermissionInfo *getRTEPermissionInfo(List *rteperminfos,
+											   RangeTblEntry *rte);
 extern bool isLockedRefname(ParseState *pstate, const char *refname);
 extern void addNSItemToQuery(ParseState *pstate, ParseNamespaceItem *nsitem,
 							 bool addToJoinList,
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 90ecf109af..05c3680cd6 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,6 +25,7 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
+								  RTEPermissionInfo *target_perminfo,
 								  Relation target_relation);
 
 extern Query *get_view_query(Relation view);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f001ca41bb..0ed319f11d 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -41,6 +41,9 @@ typedef enum ReplaceVarsNoMatchOption
 } ReplaceVarsNoMatchOption;
 
 
+extern List *CombineRangeTables(List *rtable1, List *rtable2,
+								List *rteperminfos2,
+								List **rteperminfos1);
 extern void OffsetVarNodes(Node *node, int offset, int sublevels_up);
 extern void ChangeVarNodes(Node *node, int rt_index, int new_index,
 						   int sublevels_up);
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index 4b4e259cd2..de1d3a4a94 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -46,7 +46,7 @@ static bool REGRESS_suset_variable2 = false;
 /* Saved hook values */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
-static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
+static ExecutorCheckPermissions_hook_type next_exec_check_perms_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 
 /* Test Object Access Type Hook hooks */
@@ -55,7 +55,7 @@ static void REGRESS_object_access_hook_str(ObjectAccessType access,
 										   int subId, void *arg);
 static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
 									   Oid objectId, int subId, void *arg);
-static bool REGRESS_exec_check_perms(List *rangeTabls, bool do_abort);
+static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
 static void REGRESS_utility_command(PlannedStmt *pstmt,
 									const char *queryString, bool readOnlyTree,
 									ProcessUtilityContext context,
@@ -219,8 +219,8 @@ _PG_init(void)
 	object_access_hook_str = REGRESS_object_access_hook_str;
 
 	/* DML permission check */
-	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
-	ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
+	next_exec_check_perms_hook = ExecutorCheckPermissions_hook;
+	ExecutorCheckPermissions_hook = REGRESS_exec_check_perms;
 
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
@@ -345,7 +345,7 @@ REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, i
 }
 
 static bool
-REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
+REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
 {
 	bool		am_super = superuser_arg(GetUserId());
 	bool		allow = true;
@@ -361,7 +361,7 @@ REGRESS_exec_check_perms(List *rangeTabls, bool do_abort)
 
 	/* Forward to next hook in the chain */
 	if (next_exec_check_perms_hook &&
-		!(*next_exec_check_perms_hook) (rangeTabls, do_abort))
+		!(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
 		allow = false;
 
 	if (allow)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 532ea36990..fb9f936d43 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3569,6 +3569,18 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 RESET SESSION AUTHORIZATION;
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+ERROR:  permission denied for table ruletest_t3
+RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
  x 
 ---
@@ -3581,6 +3593,8 @@ SELECT * FROM ruletest_t2;
 (1 row)
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 DROP USER regress_rule_user1;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index e9261da5e0..1f858129b8 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1293,11 +1293,26 @@ CREATE RULE rule1 AS ON INSERT TO ruletest_v1
 SET SESSION AUTHORIZATION regress_rule_user1;
 INSERT INTO ruletest_v1 VALUES (1);
 
+RESET SESSION AUTHORIZATION;
+
+-- Test that main query's relation's permissions are checked before
+-- the rule action's relation's.
+CREATE TABLE ruletest_t3 (x int);
+CREATE RULE rule2 AS ON UPDATE TO ruletest_t1
+    DO INSTEAD INSERT INTO ruletest_t2 VALUES (OLD.*);
+REVOKE ALL ON ruletest_t2 FROM regress_rule_user1;
+REVOKE ALL ON ruletest_t3 FROM regress_rule_user1;
+ALTER TABLE ruletest_t1 OWNER TO regress_rule_user1;
+SET SESSION AUTHORIZATION regress_rule_user1;
+UPDATE ruletest_t1 t1 SET x = 0 FROM ruletest_t3 t3 WHERE t1.x = t3.x;
+
 RESET SESSION AUTHORIZATION;
 SELECT * FROM ruletest_t1;
 SELECT * FROM ruletest_t2;
 
 DROP VIEW ruletest_v1;
+DROP RULE rule2 ON ruletest_t1;
+DROP TABLE ruletest_t3;
 DROP TABLE ruletest_t2;
 DROP TABLE ruletest_t1;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 58daeca831..a2dfd5c9da 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2187,6 +2187,7 @@ RI_ConstraintInfo
 RI_QueryHashEntry
 RI_QueryKey
 RTEKind
+RTEPermissionInfo
 RWConflict
 RWConflictPoolHeader
 Range
@@ -3264,6 +3265,7 @@ fix_scan_expr_context
 fix_upper_expr_context
 fix_windowagg_cond_context
 flatten_join_alias_vars_context
+flatten_rtes_walker_context
 float4
 float4KEY
 float8
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-06 15:19  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Alvaro Herrera @ 2022-12-06 15:19 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

I have pushed this finally.

I made two further changes:

1. there was no reason to rename ExecCheckPerms_hook, since its
   signature was changing anyway.  I reverted it to the original name.

2. I couldn't find any reason to expose ExecGetRTEPermissionInfo, and
   given that it's a one-line function, I removed it.

Maybe you had a reason to add ExecGetRTEPermissionInfo, thinking about
external callers; if so please discuss it.

I'll mark this commitfest entry as committed soon; please post the other
two patches you had in this series in a new thread.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-07 07:01  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-12-07 07:01 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Dec 7, 2022 at 12:19 AM Alvaro Herrera <[email protected]> wrote:
> I have pushed this finally.

Thanks a lot.

> I made two further changes:
>
> 1. there was no reason to rename ExecCheckPerms_hook, since its
>    signature was changing anyway.  I reverted it to the original name.

Sure, that makes sense.

> 2. I couldn't find any reason to expose ExecGetRTEPermissionInfo, and
>    given that it's a one-line function, I removed it.
>
> Maybe you had a reason to add ExecGetRTEPermissionInfo, thinking about
> external callers; if so please discuss it.

My thinking was that it might be better to have a macro/function that
takes EState, not es_rteperminfos, from the callers.  Kind of like how
there is exec_rt_fetch().  Though, that is only a cosmetic
consideration, so I don't want to insist.

> I'll mark this commitfest entry as committed soon; please post the other
> two patches you had in this series in a new thread.

Will do, thanks.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-07 08:47  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-12-07 08:47 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Dec 7, 2022 at 4:01 PM Amit Langote <[email protected]> wrote:
> On Wed, Dec 7, 2022 at 12:19 AM Alvaro Herrera <[email protected]> wrote:
> > I have pushed this finally.
>>
> > I'll mark this commitfest entry as committed soon; please post the other
> > two patches you had in this series in a new thread.
>
> Will do, thanks.

While doing that, I noticed that I had missed updating at least one
comment which still says that permission checking is done off of the
range table.  Attached patch fixes that.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] ApplyRetrieveRule-comment-thinko.patch (747B, ../../CA+HiwqGZm7hb2VAy8HGM22-fTDaQzqE6T=5GbAk=GkT9H0hJEg@mail.gmail.com/2-ApplyRetrieveRule-comment-thinko.patch)
  download | inline diff:
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index ea56ff79c8..7cf0ceacc3 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1884,8 +1884,9 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
-	 * We move the view's permission check data down to its rangetable. The
-	 * checks will actually be done against the OLD entry therein.
+	 * We move the view's permission check data down to its RTEPermissionInfo
+	 * contained in the view query, which the OLD entry in its range table
+	 * points to.
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);


^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-07 10:42  Alvaro Herrera <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Alvaro Herrera @ 2022-12-07 10:42 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-Dec-06, Alvaro Herrera wrote:

> I have pushed this finally.
> 
> I made two further changes:

Actually, I made one further change that I forgot to mention -- I
changed the API of CombineRangeTables once again; the committed patch
has it this way:

+/*
+ * CombineRangeTables
+ *         Adds the RTEs of 'src_rtable' into 'dst_rtable'
+ *
+ * This also adds the RTEPermissionInfos of 'src_perminfos' (belonging to the
+ * RTEs in 'src_rtable') into *dst_perminfos and also updates perminfoindex of
+ * the RTEs in 'src_rtable' to now point to the perminfos' indexes in
+ * *dst_perminfos.
+ *
+ * Note that this changes both 'dst_rtable' and 'dst_perminfo' destructively,
+ * so the caller should have better passed safe-to-modify copies.
+ */
+void
+CombineRangeTables(List **dst_rtable, List **dst_perminfos,
+                  List *src_rtable, List *src_perminfos)

The original one had the target rangetable first, then the source
RT+perminfos, and the target perminfos at the end.  This seemed
inconsistent and potentially confusing.  I also changed the argument
names (from using numbers to "dst/src" monikers) and removed the
behavior of returning the list: ISTM it did turn out to be a bad idea
after all.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-07 10:50  Amit Langote <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Amit Langote @ 2022-12-07 10:50 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Dec 7, 2022 at 7:43 PM Alvaro Herrera <[email protected]> wrote:
> On 2022-Dec-06, Alvaro Herrera wrote:
> > I have pushed this finally.
> >
> > I made two further changes:
>
> Actually, I made one further change that I forgot to mention -- I
> changed the API of CombineRangeTables once again; the committed patch
> has it this way:
>
> +/*
> + * CombineRangeTables
> + *         Adds the RTEs of 'src_rtable' into 'dst_rtable'
> + *
> + * This also adds the RTEPermissionInfos of 'src_perminfos' (belonging to the
> + * RTEs in 'src_rtable') into *dst_perminfos and also updates perminfoindex of
> + * the RTEs in 'src_rtable' to now point to the perminfos' indexes in
> + * *dst_perminfos.
> + *
> + * Note that this changes both 'dst_rtable' and 'dst_perminfo' destructively,
> + * so the caller should have better passed safe-to-modify copies.
> + */
> +void
> +CombineRangeTables(List **dst_rtable, List **dst_perminfos,
> +                  List *src_rtable, List *src_perminfos)
>
> The original one had the target rangetable first, then the source
> RT+perminfos, and the target perminfos at the end.  This seemed
> inconsistent and potentially confusing.  I also changed the argument
> names (from using numbers to "dst/src" monikers) and removed the
> behavior of returning the list: ISTM it did turn out to be a bad idea
> after all.

This looks better to me too.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions
@ 2022-12-07 11:44  Alvaro Herrera <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Alvaro Herrera @ 2022-12-07 11:44 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-Dec-07, Amit Langote wrote:

> While doing that, I noticed that I had missed updating at least one
> comment which still says that permission checking is done off of the
> range table.  Attached patch fixes that.

Pushed, thanks.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser)
@ 2022-12-10 20:17  Justin Pryzby <[email protected]>
  parent: Amit Langote <[email protected]>
  3 siblings, 1 reply; 73+ messages in thread

From: Justin Pryzby @ 2022-12-10 20:17 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; [email protected]

On Tue, Nov 29, 2022 at 10:37:56PM +0900, Amit Langote wrote:
> 0002 contains changes that has to do with changing how we access
> checkAsUser in some foreign table planning/execution code sites.
> Thought it might be better to describe it separately too.

This was committed as 599b33b94:
    Stop accessing checkAsUser via RTE in some cases

Which does this in a couple places in selfuncs.c:

                                if (!vardata->acl_ok &&
                                    root->append_rel_array != NULL)
                                {   
                                    AppendRelInfo *appinfo;
                                    Index       varno = index->rel->relid;

                                    appinfo = root->append_rel_array[varno];
                                    while (appinfo &&
                                           planner_rt_fetch(appinfo->parent_relid,
                                                            root)->rtekind == RTE_RELATION)
                                    {   
                                        varno = appinfo->parent_relid;
                                        appinfo = root->append_rel_array[varno];
                                    }
                                    if (varno != index->rel->relid)
                                    {   
                                        /* Repeat access check on this rel */
                                        rte = planner_rt_fetch(varno, root);
                                        Assert(rte->rtekind == RTE_RELATION);

-                                       userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+                                       userid = OidIsValid(onerel->userid) ?
+                                           onerel->userid : GetUserId();

                                        vardata->acl_ok =
                                            rte->securityQuals == NIL &&
                                            (pg_class_aclcheck(rte->relid,
                                                               userid,
                                                               ACL_SELECT) == ACLCHECK_OK);
                                    }
                                }


The original code rechecks rte->checkAsUser with the rte of the parent
rel.  The patch changed to access onerel instead, but that's not updated
after looping to find the parent.

Is that okay ?  It doesn't seem intentional, since "userid" is still
being recomputed, but based on onerel, which hasn't changed.  The
original intent (since 553d2ec27) is to recheck the parent's
"checkAsUser".  

It seems like this would matter for partitioned tables, when the
partition isn't readable, but its parent is, and accessed via a view
owned by another user?

-- 
Justin





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser)
@ 2022-12-11 09:25  Amit Langote <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 2 replies; 73+ messages in thread

From: Amit Langote @ 2022-12-11 09:25 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; [email protected]

Hi,

On Sun, Dec 11, 2022 at 5:17 AM Justin Pryzby <[email protected]> wrote:
> On Tue, Nov 29, 2022 at 10:37:56PM +0900, Amit Langote wrote:
> > 0002 contains changes that has to do with changing how we access
> > checkAsUser in some foreign table planning/execution code sites.
> > Thought it might be better to describe it separately too.
>
> This was committed as 599b33b94:
>     Stop accessing checkAsUser via RTE in some cases
>
> Which does this in a couple places in selfuncs.c:
>
>                                 if (!vardata->acl_ok &&
>                                     root->append_rel_array != NULL)
>                                 {
>                                     AppendRelInfo *appinfo;
>                                     Index       varno = index->rel->relid;
>
>                                     appinfo = root->append_rel_array[varno];
>                                     while (appinfo &&
>                                            planner_rt_fetch(appinfo->parent_relid,
>                                                             root)->rtekind == RTE_RELATION)
>                                     {
>                                         varno = appinfo->parent_relid;
>                                         appinfo = root->append_rel_array[varno];
>                                     }
>                                     if (varno != index->rel->relid)
>                                     {
>                                         /* Repeat access check on this rel */
>                                         rte = planner_rt_fetch(varno, root);
>                                         Assert(rte->rtekind == RTE_RELATION);
>
> -                                       userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
> +                                       userid = OidIsValid(onerel->userid) ?
> +                                           onerel->userid : GetUserId();
>
>                                         vardata->acl_ok =
>                                             rte->securityQuals == NIL &&
>                                             (pg_class_aclcheck(rte->relid,
>                                                                userid,
>                                                                ACL_SELECT) == ACLCHECK_OK);
>                                     }
>                                 }
>
>
> The original code rechecks rte->checkAsUser with the rte of the parent
> rel.  The patch changed to access onerel instead, but that's not updated
> after looping to find the parent.
>
> Is that okay ?  It doesn't seem intentional, since "userid" is still
> being recomputed, but based on onerel, which hasn't changed.  The
> original intent (since 553d2ec27) is to recheck the parent's
> "checkAsUser".
>
> It seems like this would matter for partitioned tables, when the
> partition isn't readable, but its parent is, and accessed via a view
> owned by another user?

Thanks for pointing this out.

I think these blocks which are rewriting userid to basically the same
value should have been removed from these sites as part of 599b33b94.
Even before that commit, the checkAsUser value should have been the
same in the RTE of both the child relation passed to these functions
and that of the root parent that's looked up by looping.  That's
because expand_single_inheritance_child(), which adds child RTEs,
copies the parent RTE's checkAsUser, that is, as of commit 599b33b94.
A later commit a61b1f74823c has removed the checkAsUser field from
RangeTblEntry.

Moreover, 599b33b94 adds some code in build_simple_rel() to set a
given rel's userid value by copying it from the parent rel, such that
the userid value would be the same in all relations in a given
inheritance tree.

I've attached 0001 to remove those extraneous code blocks and add a
comment mentioning that userid need not be recomputed.

While staring at the build_simple_rel() bit mentioned above, I
realized that this code fails to set userid correctly in the
inheritance parent rels that are child relations of subquery parent
relations, such as UNION ALL subqueries.  In that case, instead of
copying the userid (= 0) of the parent rel, the child should look up
its own RTEPermissionInfo, which should be there, and use the
checkAsUser value from there.  I've attached 0002 to fix this hole.  I
am not sure whether there's a way to add a test case for this in the
core suite.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v1-0001-Remove-some-dead-code-in-selfuncs.c.patch (2.3K, ../../CA+HiwqE0WY_AhLnGtTsY7eYebG212XWbM-D8gr2A_ToOHyCywQ@mail.gmail.com/2-v1-0001-Remove-some-dead-code-in-selfuncs.c.patch)
  download | inline diff:
From 3a0bb8ccb9c39c0d651f70d3106a962113b30b44 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Sun, 11 Dec 2022 18:12:05 +0900
Subject: [PATCH v1 1/2] Remove some dead code in selfuncs.c

RelOptInfo.userid is the same for all relations in a given
inheritance tree, so the code in examine_variable() and
example_simple_variable() that wants to repeat the ACL checks on
the root parent rel instead of a given leaf child relations need not
recompute userid too.
---
 src/backend/utils/adt/selfuncs.c | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 48858a871a..abd40a6d29 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5211,9 +5211,11 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = OidIsValid(onerel->userid) ?
-											onerel->userid : GetUserId();
-
+										/*
+										 * Fine to use the same userid as it's
+										 * same in all relations of an
+										 * inheritance tree.
+										 */
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
 											(pg_class_aclcheck(rte->relid,
@@ -5344,9 +5346,10 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = OidIsValid(onerel->userid) ?
-								onerel->userid : GetUserId();
-
+							/*
+							 * Fine to use the same userid as it's same in all
+							 * relations of an inheritance tree.
+							 */
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
 								(pg_class_aclcheck(rte->relid,
@@ -5485,9 +5488,10 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = OidIsValid(onerel->userid) ?
-					onerel->userid : GetUserId();
-
+				/*
+				 * Fine to use the same userid as it's same in all relations
+				 * of an inheritance tree.
+				 */
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
 					((pg_class_aclcheck(rte->relid, userid,
-- 
2.35.3



  [application/octet-stream] v1-0002-Fix-build_simple_rel-to-correctly-set-childrel-us.patch (1.3K, ../../CA+HiwqE0WY_AhLnGtTsY7eYebG212XWbM-D8gr2A_ToOHyCywQ@mail.gmail.com/3-v1-0002-Fix-build_simple_rel-to-correctly-set-childrel-us.patch)
  download | inline diff:
From 0c822e5e038bd9505724df2222433aa8ebb05173 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Sun, 11 Dec 2022 17:57:17 +0900
Subject: [PATCH v1 2/2] Fix build_simple_rel() to correctly set childrel
 userid

For child relations of a subquery parent baserels, build_simple_rel()
should look up RTEPermissionInfo instead of copying the parent rel's
userid, because the latter would be 0 give that it's a subquery rel.
---
 src/backend/optimizer/util/relnode.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 7085cf3c41..f7fc8079e7 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -229,9 +229,10 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 		/*
 		 * Get the userid from the relation's RTEPermissionInfo, though only
 		 * the tables mentioned in query are assigned RTEPermissionInfos.
-		 * Child relations (otherrels) simply use the parent's value.
+		 * Child relations (otherrels) simply use the parent's value, unless
+		 * the parent is a subquery base rel.
 		 */
-		if (parent == NULL)
+		if (parent == NULL || parent->rtekind != RTE_RELATION)
 		{
 			RTEPermissionInfo *perminfo;
 
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser)
@ 2022-12-11 14:21  Justin Pryzby <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 0 replies; 73+ messages in thread

From: Justin Pryzby @ 2022-12-11 14:21 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; [email protected]

On Sun, Dec 11, 2022 at 06:25:48PM +0900, Amit Langote wrote:
> On Sun, Dec 11, 2022 at 5:17 AM Justin Pryzby <[email protected]> wrote:
> > The original code rechecks rte->checkAsUser with the rte of the parent
> > rel.  The patch changed to access onerel instead, but that's not updated
> > after looping to find the parent.
> >
> > Is that okay ?  It doesn't seem intentional, since "userid" is still
> > being recomputed, but based on onerel, which hasn't changed.  The
> > original intent (since 553d2ec27) is to recheck the parent's
> > "checkAsUser".
> >
> > It seems like this would matter for partitioned tables, when the
> > partition isn't readable, but its parent is, and accessed via a view
> > owned by another user?
> 
> Thanks for pointing this out.
> 
> I think these blocks which are rewriting userid to basically the same
> value should have been removed from these sites as part of 599b33b94.

I thought maybe; thanks for checking.

Little nitpicks:

001:
Fine to use the same userid as it's same in all
=> the same

002:
give that it's a subquery rel.
=> given

-- 
Justin





^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser)
@ 2022-12-12 06:23  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 73+ messages in thread

From: Amit Langote @ 2022-12-12 06:23 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; [email protected]

On Sun, Dec 11, 2022 at 6:25 PM Amit Langote <[email protected]> wrote:
> I've attached 0001 to remove those extraneous code blocks and add a
> comment mentioning that userid need not be recomputed.
>
> While staring at the build_simple_rel() bit mentioned above, I
> realized that this code fails to set userid correctly in the
> inheritance parent rels that are child relations of subquery parent
> relations, such as UNION ALL subqueries.  In that case, instead of
> copying the userid (= 0) of the parent rel, the child should look up
> its own RTEPermissionInfo, which should be there, and use the
> checkAsUser value from there.  I've attached 0002 to fix this hole.  I
> am not sure whether there's a way to add a test case for this in the
> core suite.

Ah, I realized we could just expand the test added by 553d2ec27 with a
wrapper view (to test checkAsUser functionality) and a UNION ALL query
over the view (to test this change).

I've done that in the attached updated patch, in which I've also
addressed Justin's comments.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com


Attachments:

  [application/octet-stream] v2-0001-Remove-some-dead-code-in-selfuncs.c.patch (2.3K, ../../CA+HiwqGwirtFkVLWn1_LGE=xvT03v8_4OhT=XkjADTLuYMwayQ@mail.gmail.com/2-v2-0001-Remove-some-dead-code-in-selfuncs.c.patch)
  download | inline diff:
From 15f8dc9208176a953c3810293c3e24a3d2c61dbc Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Sun, 11 Dec 2022 18:12:05 +0900
Subject: [PATCH v2 1/2] Remove some dead code in selfuncs.c

RelOptInfo.userid is the same for all relations in a given
inheritance tree, so the code in examine_variable() and
example_simple_variable() that wants to repeat the ACL checks on
the root parent rel instead of a given leaf child relations need not
recompute userid too.
---
 src/backend/utils/adt/selfuncs.c | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 48858a871a..36dac7f0ea 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5211,9 +5211,11 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 										rte = planner_rt_fetch(varno, root);
 										Assert(rte->rtekind == RTE_RELATION);
 
-										userid = OidIsValid(onerel->userid) ?
-											onerel->userid : GetUserId();
-
+										/*
+										 * Fine to use the same userid as it's
+										 * the same in all relations of a
+										 * given inheritance tree.
+										 */
 										vardata->acl_ok =
 											rte->securityQuals == NIL &&
 											(pg_class_aclcheck(rte->relid,
@@ -5344,9 +5346,10 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 							rte = planner_rt_fetch(varno, root);
 							Assert(rte->rtekind == RTE_RELATION);
 
-							userid = OidIsValid(onerel->userid) ?
-								onerel->userid : GetUserId();
-
+							/*
+							 * Fine to use the same userid as it's the same in
+							 * all relations of a given inheritance tree.
+							 */
 							vardata->acl_ok =
 								rte->securityQuals == NIL &&
 								(pg_class_aclcheck(rte->relid,
@@ -5485,9 +5488,10 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 				rte = planner_rt_fetch(varno, root);
 				Assert(rte->rtekind == RTE_RELATION);
 
-				userid = OidIsValid(onerel->userid) ?
-					onerel->userid : GetUserId();
-
+				/*
+				 * Fine to use the same userid as it's the same in all
+				 * relations of a given inheritance tree.
+				 */
 				vardata->acl_ok =
 					rte->securityQuals == NIL &&
 					((pg_class_aclcheck(rte->relid, userid,
-- 
2.35.3



  [application/octet-stream] v2-0002-Correctly-set-userid-of-subquery-rel-s-child-rels.patch (6.2K, ../../CA+HiwqGwirtFkVLWn1_LGE=xvT03v8_4OhT=XkjADTLuYMwayQ@mail.gmail.com/3-v2-0002-Correctly-set-userid-of-subquery-rel-s-child-rels.patch)
  download | inline diff:
From c9b0198b34774da0197d96a1791ff9e52d651708 Mon Sep 17 00:00:00 2001
From: amitlan <[email protected]>
Date: Sun, 11 Dec 2022 17:57:17 +0900
Subject: [PATCH v2 2/2] Correctly set userid of subquery rel's child rels

For a subquery parent baserel's child relation, build_simple_rel()
should explicitly look up the latter's RTEPermissionInfo instead of
copying the parent rel's userid, which would be 0 given that it's a
subquery rel.

Expand the test case added in 553d2ec27 to cover both this change
and the case where an inheritance parent is accessed via a view such
that any permission checks on the parent are to be done as the view
owner.
---
 src/backend/optimizer/util/relnode.c  | 10 +++++--
 src/test/regress/expected/inherit.out | 43 +++++++++++++++++++++++++++
 src/test/regress/sql/inherit.sql      | 28 +++++++++++++++++
 3 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 7085cf3c41..8114427ecc 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -228,10 +228,14 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	{
 		/*
 		 * Get the userid from the relation's RTEPermissionInfo, though only
-		 * the tables mentioned in query are assigned RTEPermissionInfos.
-		 * Child relations (otherrels) simply use the parent's value.
+		 * the tables mentioned in the query are assigned RTEPermissionInfos.
+		 * So, for child relations (otherrels), simply use the parent's value,
+		 * unless the parent is a subquery base rel.
 		 */
-		if (parent == NULL)
+		Assert(parent == NULL ||
+			   (parent->rtekind == RTE_RELATION ||
+				parent->rtekind == RTE_SUBQUERY));
+		if (parent == NULL || parent->rtekind == RTE_SUBQUERY)
 		{
 			RTEPermissionInfo *perminfo;
 
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2d49e765de..46a4289db1 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2511,9 +2511,52 @@ explain (costs off)
                Filter: ("left"(c, 3) ~ 'a1$'::text)
 (6 rows)
 
+reset session authorization;
+-- Check with a view over permtest_parent and a UNION ALL over the view,
+-- especially that permtest_parent's permissions are checked with the role
+-- owning the view as permtest_parent's RTE's checkAsUser.
+create role regress_permtest_view_owner;
+create schema regress_permtest_schema;
+grant all on schema regress_permtest_schema to regress_permtest_view_owner;
+grant select(a,c) on permtest_parent to regress_permtest_view_owner;
+set session authorization regress_permtest_view_owner;
+create view regress_permtest_schema.permtest_parent_view as
+	select a, c from permtest_parent;
+-- Like above, the 2nd arm of UNION ALL gets a hash join due to lack of access
+-- to the expression index's stats
+explain (costs off)
+  select p2.a, p1.c
+  from regress_permtest_schema.permtest_parent_view p1
+	inner join regress_permtest_schema.permtest_parent_view p2
+  on p1.a = p2.a and p1.c ~ 'a1$'
+  union all
+  select p2.a, p1.c
+  from regress_permtest_schema.permtest_parent_view p1
+	inner join regress_permtest_schema.permtest_parent_view p2
+  on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Append
+   ->  Nested Loop
+         Join Filter: (permtest_parent.a = permtest_parent_1.a)
+         ->  Seq Scan on permtest_grandchild permtest_parent
+               Filter: (c ~ 'a1$'::text)
+         ->  Seq Scan on permtest_grandchild permtest_parent_1
+   ->  Hash Join
+         Hash Cond: (permtest_parent_3.a = permtest_parent_2.a)
+         ->  Seq Scan on permtest_grandchild permtest_parent_3
+         ->  Hash
+               ->  Seq Scan on permtest_grandchild permtest_parent_2
+                     Filter: ("left"(c, 3) ~ 'a1$'::text)
+(12 rows)
+
 reset session authorization;
 revoke all on permtest_parent from regress_no_child_access;
 drop role regress_no_child_access;
+revoke all on permtest_parent from regress_permtest_view_owner;
+drop view regress_permtest_schema.permtest_parent_view;
+drop schema regress_permtest_schema;
+drop role regress_permtest_view_owner;
 drop table permtest_parent;
 -- Verify that constraint errors across partition root / child are
 -- handled correctly (Bug #16293)
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 195aedb5ff..d2363768bc 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -907,8 +907,36 @@ explain (costs off)
   select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2
   on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
 reset session authorization;
+
+-- Check with a view over permtest_parent and a UNION ALL over the view,
+-- especially that permtest_parent's permissions are checked with the role
+-- owning the view as permtest_parent's RTE's checkAsUser.
+create role regress_permtest_view_owner;
+create schema regress_permtest_schema;
+grant all on schema regress_permtest_schema to regress_permtest_view_owner;
+grant select(a,c) on permtest_parent to regress_permtest_view_owner;
+set session authorization regress_permtest_view_owner;
+create view regress_permtest_schema.permtest_parent_view as
+	select a, c from permtest_parent;
+-- Like above, the 2nd arm of UNION ALL gets a hash join due to lack of access
+-- to the expression index's stats
+explain (costs off)
+  select p2.a, p1.c
+  from regress_permtest_schema.permtest_parent_view p1
+	inner join regress_permtest_schema.permtest_parent_view p2
+  on p1.a = p2.a and p1.c ~ 'a1$'
+  union all
+  select p2.a, p1.c
+  from regress_permtest_schema.permtest_parent_view p1
+	inner join regress_permtest_schema.permtest_parent_view p2
+  on p1.a = p2.a and left(p1.c, 3) ~ 'a1$';
+reset session authorization;
 revoke all on permtest_parent from regress_no_child_access;
 drop role regress_no_child_access;
+revoke all on permtest_parent from regress_permtest_view_owner;
+drop view regress_permtest_schema.permtest_parent_view;
+drop schema regress_permtest_schema;
+drop role regress_permtest_view_owner;
 drop table permtest_parent;
 
 -- Verify that constraint errors across partition root / child are
-- 
2.35.3



^ permalink  raw  reply  [nested|flat] 73+ messages in thread

* Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser)
@ 2022-12-21 19:44  Justin Pryzby <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 0 replies; 73+ messages in thread

From: Justin Pryzby @ 2022-12-21 19:44 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ian Lawrence Barwick <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Greg Stark <[email protected]>; Julien Rouhaud <[email protected]>; [email protected]

Alvaro could you comment on this ?





^ permalink  raw  reply  [nested|flat] 73+ messages in thread


end of thread, other threads:[~2022-12-21 19:44 UTC | newest]

Thread overview: 73+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-09-12 13:07 [PATCH 1/9] Pass all scan keys to BRIN consistent function at once Tomas Vondra <[email protected]>
2021-06-30 13:33 ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-06-30 14:33 ` Re: ExecRTCheckPerms() and many prunable partitions David Rowley <[email protected]>
2021-06-30 14:58   ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-06-30 22:51     ` Re: ExecRTCheckPerms() and many prunable partitions David Rowley <[email protected]>
2021-07-01 15:45 ` Re: ExecRTCheckPerms() and many prunable partitions Tom Lane <[email protected]>
2021-07-02 00:40   ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-07-07 04:41     ` Re: ExecRTCheckPerms() and many prunable partitions David Rowley <[email protected]>
2021-07-07 07:12       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-07-29 08:40     ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-08-20 13:46       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-08-26 09:13         ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-09-06 19:35           ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2021-09-10 03:22             ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2021-12-20 07:13               ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-03-23 07:03                 ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-03-23 09:49                   ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-03-23 21:02                   ` Re: ExecRTCheckPerms() and many prunable partitions Zhihong Yu <[email protected]>
2022-03-24 19:46                   ` Re: ExecRTCheckPerms() and many prunable partitions David Rowley <[email protected]>
2022-03-31 03:16                     ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-04-11 05:41                       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-07-06 03:25                         ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-07-13 08:00                           ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-07-27 03:14                             ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-07-27 21:04                               ` Re: ExecRTCheckPerms() and many prunable partitions Tom Lane <[email protected]>
2022-07-27 21:18                                 ` Re: ExecRTCheckPerms() and many prunable partitions Tom Lane <[email protected]>
2022-10-04 03:45                                   ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-04 03:54                                     ` Re: ExecRTCheckPerms() and many prunable partitions Tom Lane <[email protected]>
2022-10-04 04:11                                       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-06 13:29                                         ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-07 01:04                                           ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-07 04:25                                             ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-07 06:49                                               ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-07 07:31                                                 ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-15 06:00                                                   ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-03 23:46                                                     ` Re: ExecRTCheckPerms() and many prunable partitions Ian Lawrence Barwick <[email protected]>
2022-11-07 07:03                                                       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-10 11:58                                                         ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-11-16 11:44                                                           ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-11-21 12:03                                                           ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-21 12:46                                                             ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-25 11:28                                                             ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-29 09:27                                                               ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-11-29 13:37                                                                 ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-29 18:04                                                                   ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-11-30 02:56                                                                     ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-30 06:45                                                                       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-30 08:32                                                                   ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-12-02 07:41                                                                     ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-02 07:44                                                                       ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-02 09:59                                                                       ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-12-02 11:13                                                                         ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-05 03:09                                                                           ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-06 15:19                                                                             ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-12-07 07:01                                                                               ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-07 08:47                                                                                 ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-07 11:44                                                                                   ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-12-07 10:42                                                                               ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-12-07 10:50                                                                                 ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-12-01 10:49                                                                   ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-12-10 20:17                                                                   ` Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser) Justin Pryzby <[email protected]>
2022-12-11 09:25                                                                     ` Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser) Amit Langote <[email protected]>
2022-12-11 14:21                                                                       ` Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser) Justin Pryzby <[email protected]>
2022-12-12 06:23                                                                       ` Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser) Amit Langote <[email protected]>
2022-12-21 19:44                                                                         ` Re: ExecRTCheckPerms() and many prunable partitions (checkAsUser) Justin Pryzby <[email protected]>
2022-10-12 13:49                                           ` Re: ExecRTCheckPerms() and many prunable partitions Peter Eisentraut <[email protected]>
2022-10-13 08:14                                             ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-11-10 12:19                                           ` Re: ExecRTCheckPerms() and many prunable partitions Alvaro Herrera <[email protected]>
2022-11-11 16:46                                             ` Re: ExecRTCheckPerms() and many prunable partitions Tom Lane <[email protected]>
2022-11-14 07:32                                               ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-09-07 09:23                                 ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>
2022-10-02 17:10                                   ` Re: ExecRTCheckPerms() and many prunable partitions Andres Freund <[email protected]>
2022-10-03 09:10                                     ` Re: ExecRTCheckPerms() and many prunable partitions Amit Langote <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox